xref: /rk3399_rockchip-uboot/tools/patman/patchstream.py (revision 8cb3ce64f936f5dedbcfc1935c5caf31bb682474)
10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors.
20d24de9dSSimon Glass#
31a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
40d24de9dSSimon Glass#
50d24de9dSSimon Glass
635ce2dc4SWu, Joshimport math
70d24de9dSSimon Glassimport os
80d24de9dSSimon Glassimport re
90d24de9dSSimon Glassimport shutil
100d24de9dSSimon Glassimport tempfile
110d24de9dSSimon Glass
120d24de9dSSimon Glassimport command
130d24de9dSSimon Glassimport commit
140d24de9dSSimon Glassimport gitutil
150d24de9dSSimon Glassfrom series import Series
160d24de9dSSimon Glass
170d24de9dSSimon Glass# Tags that we detect and remove
18619dd5deSSimon Glassre_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Change-Id:|^Review URL:'
193fefd5efSSimon Glass    '|Reviewed-on:|Commit-\w*:')
200d24de9dSSimon Glass
210d24de9dSSimon Glass# Lines which are allowed after a TEST= line
220d24de9dSSimon Glassre_allowed_after_test = re.compile('^Signed-off-by:')
230d24de9dSSimon Glass
2405e5b735SIlya Yanok# Signoffs
25102061bdSSimon Glassre_signoff = re.compile('^Signed-off-by: *(.*)')
2605e5b735SIlya Yanok
270d24de9dSSimon Glass# The start of the cover letter
280d24de9dSSimon Glassre_cover = re.compile('^Cover-letter:')
290d24de9dSSimon Glass
30fe2f8d9eSSimon Glass# A cover letter Cc
31fe2f8d9eSSimon Glassre_cover_cc = re.compile('^Cover-letter-cc: *(.*)')
32fe2f8d9eSSimon Glass
330d24de9dSSimon Glass# Patch series tag
345c8fdd91SAlbert ARIBAUDre_series_tag = re.compile('^Series-([a-z-]*): *(.*)')
355c8fdd91SAlbert ARIBAUD
365c8fdd91SAlbert ARIBAUD# Commit series tag
375c8fdd91SAlbert ARIBAUDre_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)')
380d24de9dSSimon Glass
390d24de9dSSimon Glass# Commit tags that we want to collect and keep
40659c89daSSimon Glassre_tag = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc): (.*)')
410d24de9dSSimon Glass
420d24de9dSSimon Glass# The start of a new commit in the git log
4368618281SDoug Andersonre_commit = re.compile('^commit ([0-9a-f]*)$')
440d24de9dSSimon Glass
450d24de9dSSimon Glass# We detect these since checkpatch doesn't always do it
460d24de9dSSimon Glassre_space_before_tab = re.compile('^[+].* \t')
470d24de9dSSimon Glass
480d24de9dSSimon Glass# States we can be in - can we use range() and still have comments?
490d24de9dSSimon GlassSTATE_MSG_HEADER = 0        # Still in the message header
500d24de9dSSimon GlassSTATE_PATCH_SUBJECT = 1     # In patch subject (first line of log for a commit)
510d24de9dSSimon GlassSTATE_PATCH_HEADER = 2      # In patch header (after the subject)
520d24de9dSSimon GlassSTATE_DIFFS = 3             # In the diff part (past --- line)
530d24de9dSSimon Glass
540d24de9dSSimon Glassclass PatchStream:
550d24de9dSSimon Glass    """Class for detecting/injecting tags in a patch or series of patches
560d24de9dSSimon Glass
570d24de9dSSimon Glass    We support processing the output of 'git log' to read out the tags we
580d24de9dSSimon Glass    are interested in. We can also process a patch file in order to remove
590d24de9dSSimon Glass    unwanted tags or inject additional ones. These correspond to the two
600d24de9dSSimon Glass    phases of processing.
610d24de9dSSimon Glass    """
620d24de9dSSimon Glass    def __init__(self, series, name=None, is_log=False):
630d24de9dSSimon Glass        self.skip_blank = False          # True to skip a single blank line
640d24de9dSSimon Glass        self.found_test = False          # Found a TEST= line
650d24de9dSSimon Glass        self.lines_after_test = 0        # MNumber of lines found after TEST=
660d24de9dSSimon Glass        self.warn = []                   # List of warnings we have collected
670d24de9dSSimon Glass        self.linenum = 1                 # Output line number we are up to
680d24de9dSSimon Glass        self.in_section = None           # Name of start...END section we are in
690d24de9dSSimon Glass        self.notes = []                  # Series notes
700d24de9dSSimon Glass        self.section = []                # The current section...END section
710d24de9dSSimon Glass        self.series = series             # Info about the patch series
720d24de9dSSimon Glass        self.is_log = is_log             # True if indent like git log
730d24de9dSSimon Glass        self.in_change = 0               # Non-zero if we are in a change list
740d24de9dSSimon Glass        self.blank_count = 0             # Number of blank lines stored up
750d24de9dSSimon Glass        self.state = STATE_MSG_HEADER    # What state are we in?
760d24de9dSSimon Glass        self.signoff = []                # Contents of signoff line
770d24de9dSSimon Glass        self.commit = None               # Current commit
780d24de9dSSimon Glass
790d24de9dSSimon Glass    def AddToSeries(self, line, name, value):
800d24de9dSSimon Glass        """Add a new Series-xxx tag.
810d24de9dSSimon Glass
820d24de9dSSimon Glass        When a Series-xxx tag is detected, we come here to record it, if we
830d24de9dSSimon Glass        are scanning a 'git log'.
840d24de9dSSimon Glass
850d24de9dSSimon Glass        Args:
860d24de9dSSimon Glass            line: Source line containing tag (useful for debug/error messages)
870d24de9dSSimon Glass            name: Tag name (part after 'Series-')
880d24de9dSSimon Glass            value: Tag value (part after 'Series-xxx: ')
890d24de9dSSimon Glass        """
900d24de9dSSimon Glass        if name == 'notes':
910d24de9dSSimon Glass            self.in_section = name
920d24de9dSSimon Glass            self.skip_blank = False
930d24de9dSSimon Glass        if self.is_log:
940d24de9dSSimon Glass            self.series.AddTag(self.commit, line, name, value)
950d24de9dSSimon Glass
965c8fdd91SAlbert ARIBAUD    def AddToCommit(self, line, name, value):
975c8fdd91SAlbert ARIBAUD        """Add a new Commit-xxx tag.
985c8fdd91SAlbert ARIBAUD
995c8fdd91SAlbert ARIBAUD        When a Commit-xxx tag is detected, we come here to record it.
1005c8fdd91SAlbert ARIBAUD
1015c8fdd91SAlbert ARIBAUD        Args:
1025c8fdd91SAlbert ARIBAUD            line: Source line containing tag (useful for debug/error messages)
1035c8fdd91SAlbert ARIBAUD            name: Tag name (part after 'Commit-')
1045c8fdd91SAlbert ARIBAUD            value: Tag value (part after 'Commit-xxx: ')
1055c8fdd91SAlbert ARIBAUD        """
1065c8fdd91SAlbert ARIBAUD        if name == 'notes':
1075c8fdd91SAlbert ARIBAUD            self.in_section = 'commit-' + name
1085c8fdd91SAlbert ARIBAUD            self.skip_blank = False
1095c8fdd91SAlbert ARIBAUD
1100d24de9dSSimon Glass    def CloseCommit(self):
1110d24de9dSSimon Glass        """Save the current commit into our commit list, and reset our state"""
1120d24de9dSSimon Glass        if self.commit and self.is_log:
1130d24de9dSSimon Glass            self.series.AddCommit(self.commit)
1140d24de9dSSimon Glass            self.commit = None
1150d577187SBin Meng        # If 'END' is missing in a 'Cover-letter' section, and that section
1160d577187SBin Meng        # happens to show up at the very end of the commit message, this is
1170d577187SBin Meng        # the chance for us to fix it up.
1180d577187SBin Meng        if self.in_section == 'cover' and self.is_log:
1190d577187SBin Meng            self.series.cover = self.section
1200d577187SBin Meng            self.in_section = None
1210d577187SBin Meng            self.skip_blank = True
1220d577187SBin Meng            self.section = []
1230d24de9dSSimon Glass
1240d24de9dSSimon Glass    def ProcessLine(self, line):
1250d24de9dSSimon Glass        """Process a single line of a patch file or commit log
1260d24de9dSSimon Glass
1270d24de9dSSimon Glass        This process a line and returns a list of lines to output. The list
1280d24de9dSSimon Glass        may be empty or may contain multiple output lines.
1290d24de9dSSimon Glass
1300d24de9dSSimon Glass        This is where all the complicated logic is located. The class's
1310d24de9dSSimon Glass        state is used to move between different states and detect things
1320d24de9dSSimon Glass        properly.
1330d24de9dSSimon Glass
1340d24de9dSSimon Glass        We can be in one of two modes:
1350d24de9dSSimon Glass            self.is_log == True: This is 'git log' mode, where most output is
1360d24de9dSSimon Glass                indented by 4 characters and we are scanning for tags
1370d24de9dSSimon Glass
1380d24de9dSSimon Glass            self.is_log == False: This is 'patch' mode, where we already have
1390d24de9dSSimon Glass                all the tags, and are processing patches to remove junk we
1400d24de9dSSimon Glass                don't want, and add things we think are required.
1410d24de9dSSimon Glass
1420d24de9dSSimon Glass        Args:
1430d24de9dSSimon Glass            line: text line to process
1440d24de9dSSimon Glass
1450d24de9dSSimon Glass        Returns:
1460d24de9dSSimon Glass            list of output lines, or [] if nothing should be output
1470d24de9dSSimon Glass        """
1480d24de9dSSimon Glass        # Initially we have no output. Prepare the input line string
1490d24de9dSSimon Glass        out = []
1500d24de9dSSimon Glass        line = line.rstrip('\n')
1514b89b813SScott Wood
1524b89b813SScott Wood        commit_match = re_commit.match(line) if self.is_log else None
1534b89b813SScott Wood
1540d24de9dSSimon Glass        if self.is_log:
1550d24de9dSSimon Glass            if line[:4] == '    ':
1560d24de9dSSimon Glass                line = line[4:]
1570d24de9dSSimon Glass
1580d24de9dSSimon Glass        # Handle state transition and skipping blank lines
1595c8fdd91SAlbert ARIBAUD        series_tag_match = re_series_tag.match(line)
1605c8fdd91SAlbert ARIBAUD        commit_tag_match = re_commit_tag.match(line)
161e7df218cSBin Meng        cover_match = re_cover.match(line)
162fe2f8d9eSSimon Glass        cover_cc_match = re_cover_cc.match(line)
163102061bdSSimon Glass        signoff_match = re_signoff.match(line)
1640d24de9dSSimon Glass        tag_match = None
1650d24de9dSSimon Glass        if self.state == STATE_PATCH_HEADER:
1660d24de9dSSimon Glass            tag_match = re_tag.match(line)
1670d24de9dSSimon Glass        is_blank = not line.strip()
1680d24de9dSSimon Glass        if is_blank:
1690d24de9dSSimon Glass            if (self.state == STATE_MSG_HEADER
1700d24de9dSSimon Glass                    or self.state == STATE_PATCH_SUBJECT):
1710d24de9dSSimon Glass                self.state += 1
1720d24de9dSSimon Glass
1730d24de9dSSimon Glass            # We don't have a subject in the text stream of patch files
1740d24de9dSSimon Glass            # It has its own line with a Subject: tag
1750d24de9dSSimon Glass            if not self.is_log and self.state == STATE_PATCH_SUBJECT:
1760d24de9dSSimon Glass                self.state += 1
1770d24de9dSSimon Glass        elif commit_match:
1780d24de9dSSimon Glass            self.state = STATE_MSG_HEADER
1790d24de9dSSimon Glass
18094fbd3e3SBin Meng        # If a tag is detected, or a new commit starts
18113b98d95SBin Meng        if series_tag_match or commit_tag_match or \
18294fbd3e3SBin Meng           cover_match or cover_cc_match or signoff_match or \
18394fbd3e3SBin Meng           self.state == STATE_MSG_HEADER:
18457b6b190SBin Meng            # but we are already in a section, this means 'END' is missing
18557b6b190SBin Meng            # for that section, fix it up.
18613b98d95SBin Meng            if self.in_section:
18713b98d95SBin Meng                self.warn.append("Missing 'END' in section '%s'" % self.in_section)
18813b98d95SBin Meng                if self.in_section == 'cover':
18913b98d95SBin Meng                    self.series.cover = self.section
19013b98d95SBin Meng                elif self.in_section == 'notes':
19113b98d95SBin Meng                    if self.is_log:
19213b98d95SBin Meng                        self.series.notes += self.section
19313b98d95SBin Meng                elif self.in_section == 'commit-notes':
19413b98d95SBin Meng                    if self.is_log:
19513b98d95SBin Meng                        self.commit.notes += self.section
19613b98d95SBin Meng                else:
19713b98d95SBin Meng                    self.warn.append("Unknown section '%s'" % self.in_section)
19813b98d95SBin Meng                self.in_section = None
19913b98d95SBin Meng                self.skip_blank = True
20013b98d95SBin Meng                self.section = []
20157b6b190SBin Meng            # but we are already in a change list, that means a blank line
20257b6b190SBin Meng            # is missing, fix it up.
20357b6b190SBin Meng            if self.in_change:
20457b6b190SBin Meng                self.warn.append("Missing 'blank line' in section 'Series-changes'")
20557b6b190SBin Meng                self.in_change = 0
20613b98d95SBin Meng
2070d24de9dSSimon Glass        # If we are in a section, keep collecting lines until we see END
2080d24de9dSSimon Glass        if self.in_section:
2090d24de9dSSimon Glass            if line == 'END':
2100d24de9dSSimon Glass                if self.in_section == 'cover':
2110d24de9dSSimon Glass                    self.series.cover = self.section
2120d24de9dSSimon Glass                elif self.in_section == 'notes':
2130d24de9dSSimon Glass                    if self.is_log:
2140d24de9dSSimon Glass                        self.series.notes += self.section
2155c8fdd91SAlbert ARIBAUD                elif self.in_section == 'commit-notes':
2165c8fdd91SAlbert ARIBAUD                    if self.is_log:
2175c8fdd91SAlbert ARIBAUD                        self.commit.notes += self.section
2180d24de9dSSimon Glass                else:
2190d24de9dSSimon Glass                    self.warn.append("Unknown section '%s'" % self.in_section)
2200d24de9dSSimon Glass                self.in_section = None
2210d24de9dSSimon Glass                self.skip_blank = True
2220d24de9dSSimon Glass                self.section = []
2230d24de9dSSimon Glass            else:
2240d24de9dSSimon Glass                self.section.append(line)
2250d24de9dSSimon Glass
2260d24de9dSSimon Glass        # Detect the commit subject
2270d24de9dSSimon Glass        elif not is_blank and self.state == STATE_PATCH_SUBJECT:
2280d24de9dSSimon Glass            self.commit.subject = line
2290d24de9dSSimon Glass
2300d24de9dSSimon Glass        # Detect the tags we want to remove, and skip blank lines
2315c8fdd91SAlbert ARIBAUD        elif re_remove.match(line) and not commit_tag_match:
2320d24de9dSSimon Glass            self.skip_blank = True
2330d24de9dSSimon Glass
2340d24de9dSSimon Glass            # TEST= should be the last thing in the commit, so remove
2350d24de9dSSimon Glass            # everything after it
2360d24de9dSSimon Glass            if line.startswith('TEST='):
2370d24de9dSSimon Glass                self.found_test = True
2380d24de9dSSimon Glass        elif self.skip_blank and is_blank:
2390d24de9dSSimon Glass            self.skip_blank = False
2400d24de9dSSimon Glass
2410d24de9dSSimon Glass        # Detect the start of a cover letter section
242e7df218cSBin Meng        elif cover_match:
2430d24de9dSSimon Glass            self.in_section = 'cover'
2440d24de9dSSimon Glass            self.skip_blank = False
2450d24de9dSSimon Glass
246fe2f8d9eSSimon Glass        elif cover_cc_match:
247fe2f8d9eSSimon Glass            value = cover_cc_match.group(1)
248fe2f8d9eSSimon Glass            self.AddToSeries(line, 'cover-cc', value)
249fe2f8d9eSSimon Glass
2500d24de9dSSimon Glass        # If we are in a change list, key collected lines until a blank one
2510d24de9dSSimon Glass        elif self.in_change:
2520d24de9dSSimon Glass            if is_blank:
2530d24de9dSSimon Glass                # Blank line ends this change list
2540d24de9dSSimon Glass                self.in_change = 0
255102061bdSSimon Glass            elif line == '---':
25605e5b735SIlya Yanok                self.in_change = 0
25705e5b735SIlya Yanok                out = self.ProcessLine(line)
2580d24de9dSSimon Glass            else:
259a8840cb2SIlya Yanok                if self.is_log:
2600d24de9dSSimon Glass                    self.series.AddChange(self.in_change, self.commit, line)
2610d24de9dSSimon Glass            self.skip_blank = False
2620d24de9dSSimon Glass
2630d24de9dSSimon Glass        # Detect Series-xxx tags
2645c8fdd91SAlbert ARIBAUD        elif series_tag_match:
2655c8fdd91SAlbert ARIBAUD            name = series_tag_match.group(1)
2665c8fdd91SAlbert ARIBAUD            value = series_tag_match.group(2)
2670d24de9dSSimon Glass            if name == 'changes':
2680d24de9dSSimon Glass                # value is the version number: e.g. 1, or 2
2690d24de9dSSimon Glass                try:
2700d24de9dSSimon Glass                    value = int(value)
2710d24de9dSSimon Glass                except ValueError as str:
2720d24de9dSSimon Glass                    raise ValueError("%s: Cannot decode version info '%s'" %
2730d24de9dSSimon Glass                        (self.commit.hash, line))
2740d24de9dSSimon Glass                self.in_change = int(value)
2750d24de9dSSimon Glass            else:
2760d24de9dSSimon Glass                self.AddToSeries(line, name, value)
2770d24de9dSSimon Glass                self.skip_blank = True
2780d24de9dSSimon Glass
2795c8fdd91SAlbert ARIBAUD        # Detect Commit-xxx tags
2805c8fdd91SAlbert ARIBAUD        elif commit_tag_match:
2815c8fdd91SAlbert ARIBAUD            name = commit_tag_match.group(1)
2825c8fdd91SAlbert ARIBAUD            value = commit_tag_match.group(2)
2835c8fdd91SAlbert ARIBAUD            if name == 'notes':
2845c8fdd91SAlbert ARIBAUD                self.AddToCommit(line, name, value)
2855c8fdd91SAlbert ARIBAUD                self.skip_blank = True
2865c8fdd91SAlbert ARIBAUD
2870d24de9dSSimon Glass        # Detect the start of a new commit
2880d24de9dSSimon Glass        elif commit_match:
2890d24de9dSSimon Glass            self.CloseCommit()
2900b5b409aSSimon Glass            self.commit = commit.Commit(commit_match.group(1))
2910d24de9dSSimon Glass
2920d24de9dSSimon Glass        # Detect tags in the commit message
2930d24de9dSSimon Glass        elif tag_match:
2940d24de9dSSimon Glass            # Remove Tested-by self, since few will take much notice
295c7379149SIlya Yanok            if (tag_match.group(1) == 'Tested-by' and
2960d24de9dSSimon Glass                    tag_match.group(2).find(os.getenv('USER') + '@') != -1):
2970d24de9dSSimon Glass                self.warn.append("Ignoring %s" % line)
298659c89daSSimon Glass            elif tag_match.group(1) == 'Patch-cc':
2990d24de9dSSimon Glass                self.commit.AddCc(tag_match.group(2).split(','))
3000d24de9dSSimon Glass            else:
301d0c5719dSSimon Glass                out = [line]
3020d24de9dSSimon Glass
303102061bdSSimon Glass        # Suppress duplicate signoffs
304102061bdSSimon Glass        elif signoff_match:
305e752edcbSSimon Glass            if (self.is_log or not self.commit or
3066be6b6bcSSimon Glass                self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
307102061bdSSimon Glass                out = [line]
308102061bdSSimon Glass
3090d24de9dSSimon Glass        # Well that means this is an ordinary line
3100d24de9dSSimon Glass        else:
3110d24de9dSSimon Glass            # Look for space before tab
3120d24de9dSSimon Glass            m = re_space_before_tab.match(line)
3130d24de9dSSimon Glass            if m:
3140d24de9dSSimon Glass                self.warn.append('Line %d/%d has space before tab' %
3150d24de9dSSimon Glass                    (self.linenum, m.start()))
3160d24de9dSSimon Glass
3170d24de9dSSimon Glass            # OK, we have a valid non-blank line
3180d24de9dSSimon Glass            out = [line]
3190d24de9dSSimon Glass            self.linenum += 1
3200d24de9dSSimon Glass            self.skip_blank = False
3210d24de9dSSimon Glass            if self.state == STATE_DIFFS:
3220d24de9dSSimon Glass                pass
3230d24de9dSSimon Glass
3240d24de9dSSimon Glass            # If this is the start of the diffs section, emit our tags and
3250d24de9dSSimon Glass            # change log
3260d24de9dSSimon Glass            elif line == '---':
3270d24de9dSSimon Glass                self.state = STATE_DIFFS
3280d24de9dSSimon Glass
3290d24de9dSSimon Glass                # Output the tags (signeoff first), then change list
3300d24de9dSSimon Glass                out = []
3310d24de9dSSimon Glass                log = self.series.MakeChangeLog(self.commit)
332e752edcbSSimon Glass                out += [line]
333e752edcbSSimon Glass                if self.commit:
334e752edcbSSimon Glass                    out += self.commit.notes
335e752edcbSSimon Glass                out += [''] + log
3360d24de9dSSimon Glass            elif self.found_test:
3370d24de9dSSimon Glass                if not re_allowed_after_test.match(line):
3380d24de9dSSimon Glass                    self.lines_after_test += 1
3390d24de9dSSimon Glass
3400d24de9dSSimon Glass        return out
3410d24de9dSSimon Glass
3420d24de9dSSimon Glass    def Finalize(self):
3430d24de9dSSimon Glass        """Close out processing of this patch stream"""
3440d24de9dSSimon Glass        self.CloseCommit()
3450d24de9dSSimon Glass        if self.lines_after_test:
3460d24de9dSSimon Glass            self.warn.append('Found %d lines after TEST=' %
3470d24de9dSSimon Glass                    self.lines_after_test)
3480d24de9dSSimon Glass
3490d24de9dSSimon Glass    def ProcessStream(self, infd, outfd):
3500d24de9dSSimon Glass        """Copy a stream from infd to outfd, filtering out unwanting things.
3510d24de9dSSimon Glass
3520d24de9dSSimon Glass        This is used to process patch files one at a time.
3530d24de9dSSimon Glass
3540d24de9dSSimon Glass        Args:
3550d24de9dSSimon Glass            infd: Input stream file object
3560d24de9dSSimon Glass            outfd: Output stream file object
3570d24de9dSSimon Glass        """
3580d24de9dSSimon Glass        # Extract the filename from each diff, for nice warnings
3590d24de9dSSimon Glass        fname = None
3600d24de9dSSimon Glass        last_fname = None
3610d24de9dSSimon Glass        re_fname = re.compile('diff --git a/(.*) b/.*')
3620d24de9dSSimon Glass        while True:
3630d24de9dSSimon Glass            line = infd.readline()
3640d24de9dSSimon Glass            if not line:
3650d24de9dSSimon Glass                break
3660d24de9dSSimon Glass            out = self.ProcessLine(line)
3670d24de9dSSimon Glass
3680d24de9dSSimon Glass            # Try to detect blank lines at EOF
3690d24de9dSSimon Glass            for line in out:
3700d24de9dSSimon Glass                match = re_fname.match(line)
3710d24de9dSSimon Glass                if match:
3720d24de9dSSimon Glass                    last_fname = fname
3730d24de9dSSimon Glass                    fname = match.group(1)
3740d24de9dSSimon Glass                if line == '+':
3750d24de9dSSimon Glass                    self.blank_count += 1
3760d24de9dSSimon Glass                else:
3770d24de9dSSimon Glass                    if self.blank_count and (line == '-- ' or match):
3780d24de9dSSimon Glass                        self.warn.append("Found possible blank line(s) at "
3790d24de9dSSimon Glass                                "end of file '%s'" % last_fname)
3800d24de9dSSimon Glass                    outfd.write('+\n' * self.blank_count)
3810d24de9dSSimon Glass                    outfd.write(line + '\n')
3820d24de9dSSimon Glass                    self.blank_count = 0
3830d24de9dSSimon Glass        self.Finalize()
3840d24de9dSSimon Glass
3850d24de9dSSimon Glass
386e62f905eSSimon Glassdef GetMetaDataForList(commit_range, git_dir=None, count=None,
387950a2313SSimon Glass                       series = None, allow_overwrite=False):
388e62f905eSSimon Glass    """Reads out patch series metadata from the commits
389e62f905eSSimon Glass
390e62f905eSSimon Glass    This does a 'git log' on the relevant commits and pulls out the tags we
391e62f905eSSimon Glass    are interested in.
392e62f905eSSimon Glass
393e62f905eSSimon Glass    Args:
394e62f905eSSimon Glass        commit_range: Range of commits to count (e.g. 'HEAD..base')
395e62f905eSSimon Glass        git_dir: Path to git repositiory (None to use default)
396e62f905eSSimon Glass        count: Number of commits to list, or None for no limit
397e62f905eSSimon Glass        series: Series object to add information into. By default a new series
398e62f905eSSimon Glass            is started.
399950a2313SSimon Glass        allow_overwrite: Allow tags to overwrite an existing tag
400e62f905eSSimon Glass    Returns:
401e62f905eSSimon Glass        A Series object containing information about the commits.
402e62f905eSSimon Glass    """
403891b7a07SSimon Glass    if not series:
404891b7a07SSimon Glass        series = Series()
405950a2313SSimon Glass    series.allow_overwrite = allow_overwrite
406cda2a611SSimon Glass    params = gitutil.LogCmd(commit_range, reverse=True, count=count,
407cda2a611SSimon Glass                            git_dir=git_dir)
408cda2a611SSimon Glass    stdout = command.RunPipe([params], capture=True).stdout
409e62f905eSSimon Glass    ps = PatchStream(series, is_log=True)
410e62f905eSSimon Glass    for line in stdout.splitlines():
411e62f905eSSimon Glass        ps.ProcessLine(line)
412e62f905eSSimon Glass    ps.Finalize()
413e62f905eSSimon Glass    return series
414e62f905eSSimon Glass
4150d24de9dSSimon Glassdef GetMetaData(start, count):
4160d24de9dSSimon Glass    """Reads out patch series metadata from the commits
4170d24de9dSSimon Glass
4180d24de9dSSimon Glass    This does a 'git log' on the relevant commits and pulls out the tags we
4190d24de9dSSimon Glass    are interested in.
4200d24de9dSSimon Glass
4210d24de9dSSimon Glass    Args:
4220d24de9dSSimon Glass        start: Commit to start from: 0=HEAD, 1=next one, etc.
4230d24de9dSSimon Glass        count: Number of commits to list
4240d24de9dSSimon Glass    """
425e62f905eSSimon Glass    return GetMetaDataForList('HEAD~%d' % start, None, count)
4260d24de9dSSimon Glass
427*6e87ae1cSSimon Glassdef GetMetaDataForTest(text):
428*6e87ae1cSSimon Glass    """Process metadata from a file containing a git log. Used for tests
429*6e87ae1cSSimon Glass
430*6e87ae1cSSimon Glass    Args:
431*6e87ae1cSSimon Glass        text:
432*6e87ae1cSSimon Glass    """
433*6e87ae1cSSimon Glass    series = Series()
434*6e87ae1cSSimon Glass    ps = PatchStream(series, is_log=True)
435*6e87ae1cSSimon Glass    for line in text.splitlines():
436*6e87ae1cSSimon Glass        ps.ProcessLine(line)
437*6e87ae1cSSimon Glass    ps.Finalize()
438*6e87ae1cSSimon Glass    return series
439*6e87ae1cSSimon Glass
4400d24de9dSSimon Glassdef FixPatch(backup_dir, fname, series, commit):
4410d24de9dSSimon Glass    """Fix up a patch file, by adding/removing as required.
4420d24de9dSSimon Glass
4430d24de9dSSimon Glass    We remove our tags from the patch file, insert changes lists, etc.
4440d24de9dSSimon Glass    The patch file is processed in place, and overwritten.
4450d24de9dSSimon Glass
4460d24de9dSSimon Glass    A backup file is put into backup_dir (if not None).
4470d24de9dSSimon Glass
4480d24de9dSSimon Glass    Args:
4490d24de9dSSimon Glass        fname: Filename to patch file to process
4500d24de9dSSimon Glass        series: Series information about this patch set
4510d24de9dSSimon Glass        commit: Commit object for this patch file
4520d24de9dSSimon Glass    Return:
4530d24de9dSSimon Glass        A list of errors, or [] if all ok.
4540d24de9dSSimon Glass    """
4550d24de9dSSimon Glass    handle, tmpname = tempfile.mkstemp()
4560d24de9dSSimon Glass    outfd = os.fdopen(handle, 'w')
4570d24de9dSSimon Glass    infd = open(fname, 'r')
4580d24de9dSSimon Glass    ps = PatchStream(series)
4590d24de9dSSimon Glass    ps.commit = commit
4600d24de9dSSimon Glass    ps.ProcessStream(infd, outfd)
4610d24de9dSSimon Glass    infd.close()
4620d24de9dSSimon Glass    outfd.close()
4630d24de9dSSimon Glass
4640d24de9dSSimon Glass    # Create a backup file if required
4650d24de9dSSimon Glass    if backup_dir:
4660d24de9dSSimon Glass        shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
4670d24de9dSSimon Glass    shutil.move(tmpname, fname)
4680d24de9dSSimon Glass    return ps.warn
4690d24de9dSSimon Glass
4700d24de9dSSimon Glassdef FixPatches(series, fnames):
4710d24de9dSSimon Glass    """Fix up a list of patches identified by filenames
4720d24de9dSSimon Glass
4730d24de9dSSimon Glass    The patch files are processed in place, and overwritten.
4740d24de9dSSimon Glass
4750d24de9dSSimon Glass    Args:
4760d24de9dSSimon Glass        series: The series object
4770d24de9dSSimon Glass        fnames: List of patch files to process
4780d24de9dSSimon Glass    """
4790d24de9dSSimon Glass    # Current workflow creates patches, so we shouldn't need a backup
4800d24de9dSSimon Glass    backup_dir = None  #tempfile.mkdtemp('clean-patch')
4810d24de9dSSimon Glass    count = 0
4820d24de9dSSimon Glass    for fname in fnames:
4830d24de9dSSimon Glass        commit = series.commits[count]
4840d24de9dSSimon Glass        commit.patch = fname
4850d24de9dSSimon Glass        result = FixPatch(backup_dir, fname, series, commit)
4860d24de9dSSimon Glass        if result:
487a920a17bSPaul Burton            print('%d warnings for %s:' % (len(result), fname))
4880d24de9dSSimon Glass            for warn in result:
489a920a17bSPaul Burton                print('\t', warn)
4900d24de9dSSimon Glass            print
4910d24de9dSSimon Glass        count += 1
492a920a17bSPaul Burton    print('Cleaned %d patches' % count)
4930d24de9dSSimon Glass
4940d24de9dSSimon Glassdef InsertCoverLetter(fname, series, count):
4950d24de9dSSimon Glass    """Inserts a cover letter with the required info into patch 0
4960d24de9dSSimon Glass
4970d24de9dSSimon Glass    Args:
4980d24de9dSSimon Glass        fname: Input / output filename of the cover letter file
4990d24de9dSSimon Glass        series: Series object
5000d24de9dSSimon Glass        count: Number of patches in the series
5010d24de9dSSimon Glass    """
5020d24de9dSSimon Glass    fd = open(fname, 'r')
5030d24de9dSSimon Glass    lines = fd.readlines()
5040d24de9dSSimon Glass    fd.close()
5050d24de9dSSimon Glass
5060d24de9dSSimon Glass    fd = open(fname, 'w')
5070d24de9dSSimon Glass    text = series.cover
5080d24de9dSSimon Glass    prefix = series.GetPatchPrefix()
5090d24de9dSSimon Glass    for line in lines:
5100d24de9dSSimon Glass        if line.startswith('Subject:'):
51135ce2dc4SWu, Josh            # if more than 10 or 100 patches, it should say 00/xx, 000/xxx, etc
51235ce2dc4SWu, Josh            zero_repeat = int(math.log10(count)) + 1
51335ce2dc4SWu, Josh            zero = '0' * zero_repeat
51435ce2dc4SWu, Josh            line = 'Subject: [%s %s/%d] %s\n' % (prefix, zero, count, text[0])
5150d24de9dSSimon Glass
5160d24de9dSSimon Glass        # Insert our cover letter
5170d24de9dSSimon Glass        elif line.startswith('*** BLURB HERE ***'):
5180d24de9dSSimon Glass            # First the blurb test
5190d24de9dSSimon Glass            line = '\n'.join(text[1:]) + '\n'
5200d24de9dSSimon Glass            if series.get('notes'):
5210d24de9dSSimon Glass                line += '\n'.join(series.notes) + '\n'
5220d24de9dSSimon Glass
5230d24de9dSSimon Glass            # Now the change list
5240d24de9dSSimon Glass            out = series.MakeChangeLog(None)
5250d24de9dSSimon Glass            line += '\n' + '\n'.join(out)
5260d24de9dSSimon Glass        fd.write(line)
5270d24de9dSSimon Glass    fd.close()
528