blob: ba0a13f6325ed7ee8031189803113118eea9e2a8 [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
Douglas Anderson833e4192019-09-27 09:23:56 -07005import datetime
Wu, Josh35ce2dc2015-04-03 10:51:17 +08006import math
Simon Glass0d24de92012-01-14 15:12:45 +00007import os
8import re
9import shutil
10import tempfile
11
Simon Glassbf776672020-04-17 18:09:04 -060012from patman import command
13from patman import commit
14from patman import gitutil
15from patman.series import Series
Simon Glass0d24de92012-01-14 15:12:45 +000016
17# Tags that we detect and remove
Douglas Anderson833e4192019-09-27 09:23:56 -070018re_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Review URL:'
Simon Glass3fefd5e2013-04-03 11:01:39 +000019 '|Reviewed-on:|Commit-\w*:')
Simon Glass0d24de92012-01-14 15:12:45 +000020
21# Lines which are allowed after a TEST= line
22re_allowed_after_test = re.compile('^Signed-off-by:')
23
Ilya Yanok05e5b732012-08-06 23:46:05 +000024# Signoffs
Simon Glass102061b2014-04-20 10:50:14 -060025re_signoff = re.compile('^Signed-off-by: *(.*)')
Ilya Yanok05e5b732012-08-06 23:46:05 +000026
Sean Anderson6949f702020-05-04 16:28:34 -040027# Cover letter tag
28re_cover = re.compile('^Cover-([a-z-]*): *(.*)')
Simon Glassfe2f8d92013-03-20 16:43:00 +000029
Simon Glass0d24de92012-01-14 15:12:45 +000030# Patch series tag
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010031re_series_tag = re.compile('^Series-([a-z-]*): *(.*)')
32
Douglas Anderson833e4192019-09-27 09:23:56 -070033# Change-Id will be used to generate the Message-Id and then be stripped
34re_change_id = re.compile('^Change-Id: *(.*)')
35
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +010036# Commit series tag
37re_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000038
39# Commit tags that we want to collect and keep
Simon Glass7207e2b2020-07-05 21:41:57 -060040re_tag = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc|Fixes): (.*)')
Simon Glass0d24de92012-01-14 15:12:45 +000041
42# The start of a new commit in the git log
Doug Anderson68618282013-03-01 11:11:07 +000043re_commit = re.compile('^commit ([0-9a-f]*)$')
Simon Glass0d24de92012-01-14 15:12:45 +000044
45# We detect these since checkpatch doesn't always do it
46re_space_before_tab = re.compile('^[+].* \t')
47
Sean Anderson0411fff2020-05-04 16:28:35 -040048# Match indented lines for changes
49re_leading_whitespace = re.compile('^\s')
50
Simon Glass0d24de92012-01-14 15:12:45 +000051# States we can be in - can we use range() and still have comments?
52STATE_MSG_HEADER = 0 # Still in the message header
53STATE_PATCH_SUBJECT = 1 # In patch subject (first line of log for a commit)
54STATE_PATCH_HEADER = 2 # In patch header (after the subject)
55STATE_DIFFS = 3 # In the diff part (past --- line)
56
57class PatchStream:
58 """Class for detecting/injecting tags in a patch or series of patches
59
60 We support processing the output of 'git log' to read out the tags we
61 are interested in. We can also process a patch file in order to remove
62 unwanted tags or inject additional ones. These correspond to the two
63 phases of processing.
64 """
65 def __init__(self, series, name=None, is_log=False):
66 self.skip_blank = False # True to skip a single blank line
67 self.found_test = False # Found a TEST= line
Sean Anderson6949f702020-05-04 16:28:34 -040068 self.lines_after_test = 0 # Number of lines found after TEST=
Simon Glass0d24de92012-01-14 15:12:45 +000069 self.warn = [] # List of warnings we have collected
70 self.linenum = 1 # Output line number we are up to
71 self.in_section = None # Name of start...END section we are in
72 self.notes = [] # Series notes
73 self.section = [] # The current section...END section
74 self.series = series # Info about the patch series
75 self.is_log = is_log # True if indent like git log
Sean Anderson6949f702020-05-04 16:28:34 -040076 self.in_change = None # Name of the change list we are in
77 self.change_version = 0 # Non-zero if we are in a change list
Sean Anderson0411fff2020-05-04 16:28:35 -040078 self.change_lines = [] # Lines of the current change
Simon Glass0d24de92012-01-14 15:12:45 +000079 self.blank_count = 0 # Number of blank lines stored up
80 self.state = STATE_MSG_HEADER # What state are we in?
Simon Glass0d24de92012-01-14 15:12:45 +000081 self.signoff = [] # Contents of signoff line
82 self.commit = None # Current commit
83
84 def AddToSeries(self, line, name, value):
85 """Add a new Series-xxx tag.
86
87 When a Series-xxx tag is detected, we come here to record it, if we
88 are scanning a 'git log'.
89
90 Args:
91 line: Source line containing tag (useful for debug/error messages)
92 name: Tag name (part after 'Series-')
93 value: Tag value (part after 'Series-xxx: ')
94 """
95 if name == 'notes':
96 self.in_section = name
97 self.skip_blank = False
98 if self.is_log:
99 self.series.AddTag(self.commit, line, name, value)
100
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100101 def AddToCommit(self, line, name, value):
102 """Add a new Commit-xxx tag.
103
104 When a Commit-xxx tag is detected, we come here to record it.
105
106 Args:
107 line: Source line containing tag (useful for debug/error messages)
108 name: Tag name (part after 'Commit-')
109 value: Tag value (part after 'Commit-xxx: ')
110 """
111 if name == 'notes':
112 self.in_section = 'commit-' + name
113 self.skip_blank = False
114
Simon Glass7207e2b2020-07-05 21:41:57 -0600115 def AddCommitRtag(self, rtag_type, who):
116 """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 Glass0d24de92012-01-14 15:12:45 +0000124 def CloseCommit(self):
125 """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
Sean Anderson6949f702020-05-04 16:28:34 -0400138 def ParseVersion(self, value, line):
139 """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)
150 except ValueError as str:
151 raise ValueError("%s: Cannot decode version info '%s'" %
152 (self.commit.hash, line))
153
Sean Anderson0411fff2020-05-04 16:28:35 -0400154 def FinalizeChange(self):
155 """Finalize a (multi-line) change and add it to the series or commit"""
156 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 Glass0d24de92012-01-14 15:12:45 +0000168 def ProcessLine(self, line):
169 """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
196 commit_match = re_commit.match(line) if self.is_log else None
197
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
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100203 series_tag_match = re_series_tag.match(line)
Douglas Anderson833e4192019-09-27 09:23:56 -0700204 change_id_match = re_change_id.match(line)
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100205 commit_tag_match = re_commit_tag.match(line)
Bin Menge7df2182016-06-26 23:24:28 -0700206 cover_match = re_cover.match(line)
Simon Glass102061b2014-04-20 10:50:14 -0600207 signoff_match = re_signoff.match(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400208 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:
211 tag_match = re_tag.match(line)
212 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)
Sean Anderson0411fff2020-05-04 16:28:35 -0400249 self.FinalizeChange()
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':
274 raise ValueError("'END' wihout section")
275
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
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100281 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':
299 self.AddToSeries(line, 'cover-cc', value)
300 elif name == 'changes':
301 self.in_change = 'Cover'
302 self.change_version = self.ParseVersion(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
Sean Anderson0411fff2020-05-04 16:28:35 -0400308 self.FinalizeChange()
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 == '---':
Sean Anderson0411fff2020-05-04 16:28:35 -0400312 self.FinalizeChange()
Sean Anderson6949f702020-05-04 16:28:34 -0400313 self.in_change = None
314 self.change_version = 0
Ilya Yanok05e5b732012-08-06 23:46:05 +0000315 out = self.ProcessLine(line)
Sean Anderson0411fff2020-05-04 16:28:35 -0400316 elif self.is_log:
317 if not leading_whitespace_match:
318 self.FinalizeChange()
319 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'
329 self.change_version = self.ParseVersion(value, line)
Simon Glass0d24de92012-01-14 15:12:45 +0000330 else:
331 self.AddToSeries(line, name, value)
332 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:
339 raise ValueError("%s: Two Change-Ids: '%s' vs. '%s'" %
340 (self.commit.hash, self.commit.change_id, value))
341 self.commit.change_id = value
342 self.skip_blank = True
343
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100344 # Detect Commit-xxx tags
345 elif commit_tag_match:
346 name = commit_tag_match.group(1)
347 value = commit_tag_match.group(2)
348 if name == 'notes':
349 self.AddToCommit(line, name, value)
350 self.skip_blank = True
Sean Anderson6949f702020-05-04 16:28:34 -0400351 elif name == 'changes':
352 self.in_change = 'Commit'
353 self.change_version = self.ParseVersion(value, line)
Patrick Delaunaye5ff9ab2020-07-02 19:52:54 +0200354 else:
355 self.warn.append('Line %d: Ignoring Commit-%s' %
356 (self.linenum, name))
Albert ARIBAUD5c8fdd92013-11-12 11:14:41 +0100357
Simon Glass0d24de92012-01-14 15:12:45 +0000358 # Detect the start of a new commit
359 elif commit_match:
360 self.CloseCommit()
Simon Glass0b5b4092014-10-15 02:27:00 -0600361 self.commit = commit.Commit(commit_match.group(1))
Simon Glass0d24de92012-01-14 15:12:45 +0000362
363 # Detect tags in the commit message
364 elif tag_match:
Simon Glass7207e2b2020-07-05 21:41:57 -0600365 rtag_type, who = tag_match.groups()
366 self.AddCommitRtag(rtag_type, who)
Simon Glass0d24de92012-01-14 15:12:45 +0000367 # Remove Tested-by self, since few will take much notice
Simon Glass7207e2b2020-07-05 21:41:57 -0600368 if (rtag_type == 'Tested-by' and
369 who.find(os.getenv('USER') + '@') != -1):
Simon Glass0d24de92012-01-14 15:12:45 +0000370 self.warn.append("Ignoring %s" % line)
Simon Glass7207e2b2020-07-05 21:41:57 -0600371 elif rtag_type == 'Patch-cc':
372 self.commit.AddCc(who.split(','))
Simon Glass0d24de92012-01-14 15:12:45 +0000373 else:
Simon Glassd0c57192014-08-28 09:43:38 -0600374 out = [line]
Simon Glass0d24de92012-01-14 15:12:45 +0000375
Simon Glass102061b2014-04-20 10:50:14 -0600376 # Suppress duplicate signoffs
377 elif signoff_match:
Simon Glasse752edc2014-08-28 09:43:35 -0600378 if (self.is_log or not self.commit or
Simon Glass6be6b6b2014-05-13 12:14:02 -0600379 self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
Simon Glass102061b2014-04-20 10:50:14 -0600380 out = [line]
381
Simon Glass0d24de92012-01-14 15:12:45 +0000382 # Well that means this is an ordinary line
383 else:
Simon Glass0d24de92012-01-14 15:12:45 +0000384 # Look for space before tab
385 m = re_space_before_tab.match(line)
386 if m:
387 self.warn.append('Line %d/%d has space before tab' %
388 (self.linenum, m.start()))
389
390 # OK, we have a valid non-blank line
391 out = [line]
392 self.linenum += 1
393 self.skip_blank = False
394 if self.state == STATE_DIFFS:
395 pass
396
397 # If this is the start of the diffs section, emit our tags and
398 # change log
399 elif line == '---':
400 self.state = STATE_DIFFS
401
Sean Anderson6949f702020-05-04 16:28:34 -0400402 # Output the tags (signoff first), then change list
Simon Glass0d24de92012-01-14 15:12:45 +0000403 out = []
Simon Glass0d24de92012-01-14 15:12:45 +0000404 log = self.series.MakeChangeLog(self.commit)
Simon Glasse752edc2014-08-28 09:43:35 -0600405 out += [line]
406 if self.commit:
407 out += self.commit.notes
408 out += [''] + log
Simon Glass0d24de92012-01-14 15:12:45 +0000409 elif self.found_test:
410 if not re_allowed_after_test.match(line):
411 self.lines_after_test += 1
412
413 return out
414
415 def Finalize(self):
416 """Close out processing of this patch stream"""
Sean Anderson0411fff2020-05-04 16:28:35 -0400417 self.FinalizeChange()
Simon Glass0d24de92012-01-14 15:12:45 +0000418 self.CloseCommit()
419 if self.lines_after_test:
420 self.warn.append('Found %d lines after TEST=' %
421 self.lines_after_test)
422
Douglas Anderson833e4192019-09-27 09:23:56 -0700423 def WriteMessageId(self, outfd):
424 """Write the Message-Id into the output.
425
426 This is based on the Change-Id in the original patch, the version,
427 and the prefix.
428
429 Args:
430 outfd: Output stream file object
431 """
432 if not self.commit.change_id:
433 return
434
435 # If the count is -1 we're testing, so use a fixed time
436 if self.commit.count == -1:
437 time_now = datetime.datetime(1999, 12, 31, 23, 59, 59)
438 else:
439 time_now = datetime.datetime.now()
440
441 # In theory there is email.utils.make_msgid() which would be nice
442 # to use, but it already produces something way too long and thus
443 # will produce ugly commit lines if someone throws this into
444 # a "Link:" tag in the final commit. So (sigh) roll our own.
445
446 # Start with the time; presumably we wouldn't send the same series
447 # with the same Change-Id at the exact same second.
448 parts = [time_now.strftime("%Y%m%d%H%M%S")]
449
450 # These seem like they would be nice to include.
451 if 'prefix' in self.series:
452 parts.append(self.series['prefix'])
453 if 'version' in self.series:
454 parts.append("v%s" % self.series['version'])
455
456 parts.append(str(self.commit.count + 1))
457
458 # The Change-Id must be last, right before the @
459 parts.append(self.commit.change_id)
460
461 # Join parts together with "." and write it out.
462 outfd.write('Message-Id: <%s@changeid>\n' % '.'.join(parts))
463
Simon Glass0d24de92012-01-14 15:12:45 +0000464 def ProcessStream(self, infd, outfd):
465 """Copy a stream from infd to outfd, filtering out unwanting things.
466
467 This is used to process patch files one at a time.
468
469 Args:
470 infd: Input stream file object
471 outfd: Output stream file object
472 """
473 # Extract the filename from each diff, for nice warnings
474 fname = None
475 last_fname = None
476 re_fname = re.compile('diff --git a/(.*) b/.*')
Douglas Anderson833e4192019-09-27 09:23:56 -0700477
478 self.WriteMessageId(outfd)
479
Simon Glass0d24de92012-01-14 15:12:45 +0000480 while True:
481 line = infd.readline()
482 if not line:
483 break
484 out = self.ProcessLine(line)
485
486 # Try to detect blank lines at EOF
487 for line in out:
488 match = re_fname.match(line)
489 if match:
490 last_fname = fname
491 fname = match.group(1)
492 if line == '+':
493 self.blank_count += 1
494 else:
495 if self.blank_count and (line == '-- ' or match):
496 self.warn.append("Found possible blank line(s) at "
497 "end of file '%s'" % last_fname)
498 outfd.write('+\n' * self.blank_count)
499 outfd.write(line + '\n')
500 self.blank_count = 0
501 self.Finalize()
502
503
Simon Glasse62f9052012-12-15 10:42:06 +0000504def GetMetaDataForList(commit_range, git_dir=None, count=None,
Simon Glass950a2312014-09-05 19:00:23 -0600505 series = None, allow_overwrite=False):
Simon Glasse62f9052012-12-15 10:42:06 +0000506 """Reads out patch series metadata from the commits
507
508 This does a 'git log' on the relevant commits and pulls out the tags we
509 are interested in.
510
511 Args:
512 commit_range: Range of commits to count (e.g. 'HEAD..base')
513 git_dir: Path to git repositiory (None to use default)
514 count: Number of commits to list, or None for no limit
515 series: Series object to add information into. By default a new series
516 is started.
Simon Glass950a2312014-09-05 19:00:23 -0600517 allow_overwrite: Allow tags to overwrite an existing tag
Simon Glasse62f9052012-12-15 10:42:06 +0000518 Returns:
519 A Series object containing information about the commits.
520 """
Simon Glass891b7a02014-09-05 19:00:19 -0600521 if not series:
522 series = Series()
Simon Glass950a2312014-09-05 19:00:23 -0600523 series.allow_overwrite = allow_overwrite
Simon Glass9ad96982016-03-06 19:45:33 -0700524 params = gitutil.LogCmd(commit_range, reverse=True, count=count,
Simon Glasscda2a612014-08-09 15:33:10 -0600525 git_dir=git_dir)
526 stdout = command.RunPipe([params], capture=True).stdout
Simon Glasse62f9052012-12-15 10:42:06 +0000527 ps = PatchStream(series, is_log=True)
528 for line in stdout.splitlines():
529 ps.ProcessLine(line)
530 ps.Finalize()
531 return series
532
Simon Glass262130f2020-07-05 21:41:51 -0600533def GetMetaData(branch, start, count):
Simon Glass0d24de92012-01-14 15:12:45 +0000534 """Reads out patch series metadata from the commits
535
536 This does a 'git log' on the relevant commits and pulls out the tags we
537 are interested in.
538
539 Args:
Simon Glass262130f2020-07-05 21:41:51 -0600540 branch: Branch to use (None for current branch)
541 start: Commit to start from: 0=branch HEAD, 1=next one, etc.
Simon Glass0d24de92012-01-14 15:12:45 +0000542 count: Number of commits to list
543 """
Simon Glass262130f2020-07-05 21:41:51 -0600544 return GetMetaDataForList('%s~%d' % (branch if branch else 'HEAD', start),
545 None, count)
Simon Glass0d24de92012-01-14 15:12:45 +0000546
Simon Glass6e87ae12017-05-29 15:31:31 -0600547def GetMetaDataForTest(text):
548 """Process metadata from a file containing a git log. Used for tests
549
550 Args:
551 text:
552 """
553 series = Series()
554 ps = PatchStream(series, is_log=True)
555 for line in text.splitlines():
556 ps.ProcessLine(line)
557 ps.Finalize()
558 return series
559
Simon Glass0d24de92012-01-14 15:12:45 +0000560def FixPatch(backup_dir, fname, series, commit):
561 """Fix up a patch file, by adding/removing as required.
562
563 We remove our tags from the patch file, insert changes lists, etc.
564 The patch file is processed in place, and overwritten.
565
566 A backup file is put into backup_dir (if not None).
567
568 Args:
569 fname: Filename to patch file to process
570 series: Series information about this patch set
571 commit: Commit object for this patch file
572 Return:
573 A list of errors, or [] if all ok.
574 """
575 handle, tmpname = tempfile.mkstemp()
Simon Glass272cd852019-10-31 07:42:51 -0600576 outfd = os.fdopen(handle, 'w', encoding='utf-8')
577 infd = open(fname, 'r', encoding='utf-8')
Simon Glass0d24de92012-01-14 15:12:45 +0000578 ps = PatchStream(series)
579 ps.commit = commit
580 ps.ProcessStream(infd, outfd)
581 infd.close()
582 outfd.close()
583
584 # Create a backup file if required
585 if backup_dir:
586 shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
587 shutil.move(tmpname, fname)
588 return ps.warn
589
590def FixPatches(series, fnames):
591 """Fix up a list of patches identified by filenames
592
593 The patch files are processed in place, and overwritten.
594
595 Args:
596 series: The series object
597 fnames: List of patch files to process
598 """
599 # Current workflow creates patches, so we shouldn't need a backup
600 backup_dir = None #tempfile.mkdtemp('clean-patch')
601 count = 0
602 for fname in fnames:
603 commit = series.commits[count]
604 commit.patch = fname
Douglas Anderson833e4192019-09-27 09:23:56 -0700605 commit.count = count
Simon Glass0d24de92012-01-14 15:12:45 +0000606 result = FixPatch(backup_dir, fname, series, commit)
607 if result:
Paul Burtona920a172016-09-27 16:03:50 +0100608 print('%d warnings for %s:' % (len(result), fname))
Simon Glass0d24de92012-01-14 15:12:45 +0000609 for warn in result:
Paul Burtona920a172016-09-27 16:03:50 +0100610 print('\t', warn)
Simon Glass0d24de92012-01-14 15:12:45 +0000611 print
612 count += 1
Paul Burtona920a172016-09-27 16:03:50 +0100613 print('Cleaned %d patches' % count)
Simon Glass0d24de92012-01-14 15:12:45 +0000614
615def InsertCoverLetter(fname, series, count):
616 """Inserts a cover letter with the required info into patch 0
617
618 Args:
619 fname: Input / output filename of the cover letter file
620 series: Series object
621 count: Number of patches in the series
622 """
623 fd = open(fname, 'r')
624 lines = fd.readlines()
625 fd.close()
626
627 fd = open(fname, 'w')
628 text = series.cover
629 prefix = series.GetPatchPrefix()
630 for line in lines:
631 if line.startswith('Subject:'):
Wu, Josh35ce2dc2015-04-03 10:51:17 +0800632 # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
633 zero_repeat = int(math.log10(count)) + 1
634 zero = '0' * zero_repeat
635 line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
Simon Glass0d24de92012-01-14 15:12:45 +0000636
637 # Insert our cover letter
638 elif line.startswith('*** BLURB HERE ***'):
639 # First the blurb test
640 line = '\n'.join(text[1:]) + '\n'
641 if series.get('notes'):
642 line += '\n'.join(series.notes) + '\n'
643
644 # Now the change list
645 out = series.MakeChangeLog(None)
646 line += '\n' + '\n'.join(out)
647 fd.write(line)
648 fd.close()