xref: /rk3399_rockchip-uboot/tools/patman/patchstream.py (revision 4b89b8135f4792c456fbffbf7eea8456c3472455)
10d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors.
20d24de9dSSimon Glass#
31a459660SWolfgang Denk# SPDX-License-Identifier:	GPL-2.0+
40d24de9dSSimon Glass#
50d24de9dSSimon Glass
60d24de9dSSimon Glassimport os
70d24de9dSSimon Glassimport re
80d24de9dSSimon Glassimport shutil
90d24de9dSSimon Glassimport tempfile
100d24de9dSSimon Glass
110d24de9dSSimon Glassimport command
120d24de9dSSimon Glassimport commit
130d24de9dSSimon Glassimport gitutil
140d24de9dSSimon Glassfrom series import Series
150d24de9dSSimon Glass
160d24de9dSSimon Glass# Tags that we detect and remove
17619dd5deSSimon Glassre_remove = re.compile('^BUG=|^TEST=|^BRANCH=|^Change-Id:|^Review URL:'
183fefd5efSSimon Glass    '|Reviewed-on:|Commit-\w*:')
190d24de9dSSimon Glass
200d24de9dSSimon Glass# Lines which are allowed after a TEST= line
210d24de9dSSimon Glassre_allowed_after_test = re.compile('^Signed-off-by:')
220d24de9dSSimon Glass
2305e5b735SIlya Yanok# Signoffs
24102061bdSSimon Glassre_signoff = re.compile('^Signed-off-by: *(.*)')
2505e5b735SIlya Yanok
260d24de9dSSimon Glass# The start of the cover letter
270d24de9dSSimon Glassre_cover = re.compile('^Cover-letter:')
280d24de9dSSimon Glass
29fe2f8d9eSSimon Glass# A cover letter Cc
30fe2f8d9eSSimon Glassre_cover_cc = re.compile('^Cover-letter-cc: *(.*)')
31fe2f8d9eSSimon Glass
320d24de9dSSimon Glass# Patch series tag
335c8fdd91SAlbert ARIBAUDre_series_tag = re.compile('^Series-([a-z-]*): *(.*)')
345c8fdd91SAlbert ARIBAUD
355c8fdd91SAlbert ARIBAUD# Commit series tag
365c8fdd91SAlbert ARIBAUDre_commit_tag = re.compile('^Commit-([a-z-]*): *(.*)')
370d24de9dSSimon Glass
380d24de9dSSimon Glass# Commit tags that we want to collect and keep
39659c89daSSimon Glassre_tag = re.compile('^(Tested-by|Acked-by|Reviewed-by|Patch-cc): (.*)')
400d24de9dSSimon Glass
410d24de9dSSimon Glass# The start of a new commit in the git log
4268618281SDoug Andersonre_commit = re.compile('^commit ([0-9a-f]*)$')
430d24de9dSSimon Glass
440d24de9dSSimon Glass# We detect these since checkpatch doesn't always do it
450d24de9dSSimon Glassre_space_before_tab = re.compile('^[+].* \t')
460d24de9dSSimon Glass
470d24de9dSSimon Glass# States we can be in - can we use range() and still have comments?
480d24de9dSSimon GlassSTATE_MSG_HEADER = 0        # Still in the message header
490d24de9dSSimon GlassSTATE_PATCH_SUBJECT = 1     # In patch subject (first line of log for a commit)
500d24de9dSSimon GlassSTATE_PATCH_HEADER = 2      # In patch header (after the subject)
510d24de9dSSimon GlassSTATE_DIFFS = 3             # In the diff part (past --- line)
520d24de9dSSimon Glass
530d24de9dSSimon Glassclass PatchStream:
540d24de9dSSimon Glass    """Class for detecting/injecting tags in a patch or series of patches
550d24de9dSSimon Glass
560d24de9dSSimon Glass    We support processing the output of 'git log' to read out the tags we
570d24de9dSSimon Glass    are interested in. We can also process a patch file in order to remove
580d24de9dSSimon Glass    unwanted tags or inject additional ones. These correspond to the two
590d24de9dSSimon Glass    phases of processing.
600d24de9dSSimon Glass    """
610d24de9dSSimon Glass    def __init__(self, series, name=None, is_log=False):
620d24de9dSSimon Glass        self.skip_blank = False          # True to skip a single blank line
630d24de9dSSimon Glass        self.found_test = False          # Found a TEST= line
640d24de9dSSimon Glass        self.lines_after_test = 0        # MNumber of lines found after TEST=
650d24de9dSSimon Glass        self.warn = []                   # List of warnings we have collected
660d24de9dSSimon Glass        self.linenum = 1                 # Output line number we are up to
670d24de9dSSimon Glass        self.in_section = None           # Name of start...END section we are in
680d24de9dSSimon Glass        self.notes = []                  # Series notes
690d24de9dSSimon Glass        self.section = []                # The current section...END section
700d24de9dSSimon Glass        self.series = series             # Info about the patch series
710d24de9dSSimon Glass        self.is_log = is_log             # True if indent like git log
720d24de9dSSimon Glass        self.in_change = 0               # Non-zero if we are in a change list
730d24de9dSSimon Glass        self.blank_count = 0             # Number of blank lines stored up
740d24de9dSSimon Glass        self.state = STATE_MSG_HEADER    # What state are we in?
750d24de9dSSimon Glass        self.signoff = []                # Contents of signoff line
760d24de9dSSimon Glass        self.commit = None               # Current commit
770d24de9dSSimon Glass
780d24de9dSSimon Glass    def AddToSeries(self, line, name, value):
790d24de9dSSimon Glass        """Add a new Series-xxx tag.
800d24de9dSSimon Glass
810d24de9dSSimon Glass        When a Series-xxx tag is detected, we come here to record it, if we
820d24de9dSSimon Glass        are scanning a 'git log'.
830d24de9dSSimon Glass
840d24de9dSSimon Glass        Args:
850d24de9dSSimon Glass            line: Source line containing tag (useful for debug/error messages)
860d24de9dSSimon Glass            name: Tag name (part after 'Series-')
870d24de9dSSimon Glass            value: Tag value (part after 'Series-xxx: ')
880d24de9dSSimon Glass        """
890d24de9dSSimon Glass        if name == 'notes':
900d24de9dSSimon Glass            self.in_section = name
910d24de9dSSimon Glass            self.skip_blank = False
920d24de9dSSimon Glass        if self.is_log:
930d24de9dSSimon Glass            self.series.AddTag(self.commit, line, name, value)
940d24de9dSSimon Glass
955c8fdd91SAlbert ARIBAUD    def AddToCommit(self, line, name, value):
965c8fdd91SAlbert ARIBAUD        """Add a new Commit-xxx tag.
975c8fdd91SAlbert ARIBAUD
985c8fdd91SAlbert ARIBAUD        When a Commit-xxx tag is detected, we come here to record it.
995c8fdd91SAlbert ARIBAUD
1005c8fdd91SAlbert ARIBAUD        Args:
1015c8fdd91SAlbert ARIBAUD            line: Source line containing tag (useful for debug/error messages)
1025c8fdd91SAlbert ARIBAUD            name: Tag name (part after 'Commit-')
1035c8fdd91SAlbert ARIBAUD            value: Tag value (part after 'Commit-xxx: ')
1045c8fdd91SAlbert ARIBAUD        """
1055c8fdd91SAlbert ARIBAUD        if name == 'notes':
1065c8fdd91SAlbert ARIBAUD            self.in_section = 'commit-' + name
1075c8fdd91SAlbert ARIBAUD            self.skip_blank = False
1085c8fdd91SAlbert ARIBAUD
1090d24de9dSSimon Glass    def CloseCommit(self):
1100d24de9dSSimon Glass        """Save the current commit into our commit list, and reset our state"""
1110d24de9dSSimon Glass        if self.commit and self.is_log:
1120d24de9dSSimon Glass            self.series.AddCommit(self.commit)
1130d24de9dSSimon Glass            self.commit = None
1140d24de9dSSimon Glass
1150d24de9dSSimon Glass    def ProcessLine(self, line):
1160d24de9dSSimon Glass        """Process a single line of a patch file or commit log
1170d24de9dSSimon Glass
1180d24de9dSSimon Glass        This process a line and returns a list of lines to output. The list
1190d24de9dSSimon Glass        may be empty or may contain multiple output lines.
1200d24de9dSSimon Glass
1210d24de9dSSimon Glass        This is where all the complicated logic is located. The class's
1220d24de9dSSimon Glass        state is used to move between different states and detect things
1230d24de9dSSimon Glass        properly.
1240d24de9dSSimon Glass
1250d24de9dSSimon Glass        We can be in one of two modes:
1260d24de9dSSimon Glass            self.is_log == True: This is 'git log' mode, where most output is
1270d24de9dSSimon Glass                indented by 4 characters and we are scanning for tags
1280d24de9dSSimon Glass
1290d24de9dSSimon Glass            self.is_log == False: This is 'patch' mode, where we already have
1300d24de9dSSimon Glass                all the tags, and are processing patches to remove junk we
1310d24de9dSSimon Glass                don't want, and add things we think are required.
1320d24de9dSSimon Glass
1330d24de9dSSimon Glass        Args:
1340d24de9dSSimon Glass            line: text line to process
1350d24de9dSSimon Glass
1360d24de9dSSimon Glass        Returns:
1370d24de9dSSimon Glass            list of output lines, or [] if nothing should be output
1380d24de9dSSimon Glass        """
1390d24de9dSSimon Glass        # Initially we have no output. Prepare the input line string
1400d24de9dSSimon Glass        out = []
1410d24de9dSSimon Glass        line = line.rstrip('\n')
142*4b89b813SScott Wood
143*4b89b813SScott Wood        commit_match = re_commit.match(line) if self.is_log else None
144*4b89b813SScott Wood
1450d24de9dSSimon Glass        if self.is_log:
1460d24de9dSSimon Glass            if line[:4] == '    ':
1470d24de9dSSimon Glass                line = line[4:]
1480d24de9dSSimon Glass
1490d24de9dSSimon Glass        # Handle state transition and skipping blank lines
1505c8fdd91SAlbert ARIBAUD        series_tag_match = re_series_tag.match(line)
1515c8fdd91SAlbert ARIBAUD        commit_tag_match = re_commit_tag.match(line)
152fe2f8d9eSSimon Glass        cover_cc_match = re_cover_cc.match(line)
153102061bdSSimon Glass        signoff_match = re_signoff.match(line)
1540d24de9dSSimon Glass        tag_match = None
1550d24de9dSSimon Glass        if self.state == STATE_PATCH_HEADER:
1560d24de9dSSimon Glass            tag_match = re_tag.match(line)
1570d24de9dSSimon Glass        is_blank = not line.strip()
1580d24de9dSSimon Glass        if is_blank:
1590d24de9dSSimon Glass            if (self.state == STATE_MSG_HEADER
1600d24de9dSSimon Glass                    or self.state == STATE_PATCH_SUBJECT):
1610d24de9dSSimon Glass                self.state += 1
1620d24de9dSSimon Glass
1630d24de9dSSimon Glass            # We don't have a subject in the text stream of patch files
1640d24de9dSSimon Glass            # It has its own line with a Subject: tag
1650d24de9dSSimon Glass            if not self.is_log and self.state == STATE_PATCH_SUBJECT:
1660d24de9dSSimon Glass                self.state += 1
1670d24de9dSSimon Glass        elif commit_match:
1680d24de9dSSimon Glass            self.state = STATE_MSG_HEADER
1690d24de9dSSimon Glass
1700d24de9dSSimon Glass        # If we are in a section, keep collecting lines until we see END
1710d24de9dSSimon Glass        if self.in_section:
1720d24de9dSSimon Glass            if line == 'END':
1730d24de9dSSimon Glass                if self.in_section == 'cover':
1740d24de9dSSimon Glass                    self.series.cover = self.section
1750d24de9dSSimon Glass                elif self.in_section == 'notes':
1760d24de9dSSimon Glass                    if self.is_log:
1770d24de9dSSimon Glass                        self.series.notes += self.section
1785c8fdd91SAlbert ARIBAUD                elif self.in_section == 'commit-notes':
1795c8fdd91SAlbert ARIBAUD                    if self.is_log:
1805c8fdd91SAlbert ARIBAUD                        self.commit.notes += self.section
1810d24de9dSSimon Glass                else:
1820d24de9dSSimon Glass                    self.warn.append("Unknown section '%s'" % self.in_section)
1830d24de9dSSimon Glass                self.in_section = None
1840d24de9dSSimon Glass                self.skip_blank = True
1850d24de9dSSimon Glass                self.section = []
1860d24de9dSSimon Glass            else:
1870d24de9dSSimon Glass                self.section.append(line)
1880d24de9dSSimon Glass
1890d24de9dSSimon Glass        # Detect the commit subject
1900d24de9dSSimon Glass        elif not is_blank and self.state == STATE_PATCH_SUBJECT:
1910d24de9dSSimon Glass            self.commit.subject = line
1920d24de9dSSimon Glass
1930d24de9dSSimon Glass        # Detect the tags we want to remove, and skip blank lines
1945c8fdd91SAlbert ARIBAUD        elif re_remove.match(line) and not commit_tag_match:
1950d24de9dSSimon Glass            self.skip_blank = True
1960d24de9dSSimon Glass
1970d24de9dSSimon Glass            # TEST= should be the last thing in the commit, so remove
1980d24de9dSSimon Glass            # everything after it
1990d24de9dSSimon Glass            if line.startswith('TEST='):
2000d24de9dSSimon Glass                self.found_test = True
2010d24de9dSSimon Glass        elif self.skip_blank and is_blank:
2020d24de9dSSimon Glass            self.skip_blank = False
2030d24de9dSSimon Glass
2040d24de9dSSimon Glass        # Detect the start of a cover letter section
2050d24de9dSSimon Glass        elif re_cover.match(line):
2060d24de9dSSimon Glass            self.in_section = 'cover'
2070d24de9dSSimon Glass            self.skip_blank = False
2080d24de9dSSimon Glass
209fe2f8d9eSSimon Glass        elif cover_cc_match:
210fe2f8d9eSSimon Glass            value = cover_cc_match.group(1)
211fe2f8d9eSSimon Glass            self.AddToSeries(line, 'cover-cc', value)
212fe2f8d9eSSimon Glass
2130d24de9dSSimon Glass        # If we are in a change list, key collected lines until a blank one
2140d24de9dSSimon Glass        elif self.in_change:
2150d24de9dSSimon Glass            if is_blank:
2160d24de9dSSimon Glass                # Blank line ends this change list
2170d24de9dSSimon Glass                self.in_change = 0
218102061bdSSimon Glass            elif line == '---':
21905e5b735SIlya Yanok                self.in_change = 0
22005e5b735SIlya Yanok                out = self.ProcessLine(line)
2210d24de9dSSimon Glass            else:
222a8840cb2SIlya Yanok                if self.is_log:
2230d24de9dSSimon Glass                    self.series.AddChange(self.in_change, self.commit, line)
2240d24de9dSSimon Glass            self.skip_blank = False
2250d24de9dSSimon Glass
2260d24de9dSSimon Glass        # Detect Series-xxx tags
2275c8fdd91SAlbert ARIBAUD        elif series_tag_match:
2285c8fdd91SAlbert ARIBAUD            name = series_tag_match.group(1)
2295c8fdd91SAlbert ARIBAUD            value = series_tag_match.group(2)
2300d24de9dSSimon Glass            if name == 'changes':
2310d24de9dSSimon Glass                # value is the version number: e.g. 1, or 2
2320d24de9dSSimon Glass                try:
2330d24de9dSSimon Glass                    value = int(value)
2340d24de9dSSimon Glass                except ValueError as str:
2350d24de9dSSimon Glass                    raise ValueError("%s: Cannot decode version info '%s'" %
2360d24de9dSSimon Glass                        (self.commit.hash, line))
2370d24de9dSSimon Glass                self.in_change = int(value)
2380d24de9dSSimon Glass            else:
2390d24de9dSSimon Glass                self.AddToSeries(line, name, value)
2400d24de9dSSimon Glass                self.skip_blank = True
2410d24de9dSSimon Glass
2425c8fdd91SAlbert ARIBAUD        # Detect Commit-xxx tags
2435c8fdd91SAlbert ARIBAUD        elif commit_tag_match:
2445c8fdd91SAlbert ARIBAUD            name = commit_tag_match.group(1)
2455c8fdd91SAlbert ARIBAUD            value = commit_tag_match.group(2)
2465c8fdd91SAlbert ARIBAUD            if name == 'notes':
2475c8fdd91SAlbert ARIBAUD                self.AddToCommit(line, name, value)
2485c8fdd91SAlbert ARIBAUD                self.skip_blank = True
2495c8fdd91SAlbert ARIBAUD
2500d24de9dSSimon Glass        # Detect the start of a new commit
2510d24de9dSSimon Glass        elif commit_match:
2520d24de9dSSimon Glass            self.CloseCommit()
2530b5b409aSSimon Glass            self.commit = commit.Commit(commit_match.group(1))
2540d24de9dSSimon Glass
2550d24de9dSSimon Glass        # Detect tags in the commit message
2560d24de9dSSimon Glass        elif tag_match:
2570d24de9dSSimon Glass            # Remove Tested-by self, since few will take much notice
258c7379149SIlya Yanok            if (tag_match.group(1) == 'Tested-by' and
2590d24de9dSSimon Glass                    tag_match.group(2).find(os.getenv('USER') + '@') != -1):
2600d24de9dSSimon Glass                self.warn.append("Ignoring %s" % line)
261659c89daSSimon Glass            elif tag_match.group(1) == 'Patch-cc':
2620d24de9dSSimon Glass                self.commit.AddCc(tag_match.group(2).split(','))
2630d24de9dSSimon Glass            else:
264d0c5719dSSimon Glass                out = [line]
2650d24de9dSSimon Glass
266102061bdSSimon Glass        # Suppress duplicate signoffs
267102061bdSSimon Glass        elif signoff_match:
268e752edcbSSimon Glass            if (self.is_log or not self.commit or
2696be6b6bcSSimon Glass                self.commit.CheckDuplicateSignoff(signoff_match.group(1))):
270102061bdSSimon Glass                out = [line]
271102061bdSSimon Glass
2720d24de9dSSimon Glass        # Well that means this is an ordinary line
2730d24de9dSSimon Glass        else:
2740d24de9dSSimon Glass            pos = 1
2750d24de9dSSimon Glass            # Look for ugly ASCII characters
2760d24de9dSSimon Glass            for ch in line:
2770d24de9dSSimon Glass                # TODO: Would be nicer to report source filename and line
2780d24de9dSSimon Glass                if ord(ch) > 0x80:
2790d24de9dSSimon Glass                    self.warn.append("Line %d/%d ('%s') has funny ascii char" %
2800d24de9dSSimon Glass                        (self.linenum, pos, line))
2810d24de9dSSimon Glass                pos += 1
2820d24de9dSSimon Glass
2830d24de9dSSimon Glass            # Look for space before tab
2840d24de9dSSimon Glass            m = re_space_before_tab.match(line)
2850d24de9dSSimon Glass            if m:
2860d24de9dSSimon Glass                self.warn.append('Line %d/%d has space before tab' %
2870d24de9dSSimon Glass                    (self.linenum, m.start()))
2880d24de9dSSimon Glass
2890d24de9dSSimon Glass            # OK, we have a valid non-blank line
2900d24de9dSSimon Glass            out = [line]
2910d24de9dSSimon Glass            self.linenum += 1
2920d24de9dSSimon Glass            self.skip_blank = False
2930d24de9dSSimon Glass            if self.state == STATE_DIFFS:
2940d24de9dSSimon Glass                pass
2950d24de9dSSimon Glass
2960d24de9dSSimon Glass            # If this is the start of the diffs section, emit our tags and
2970d24de9dSSimon Glass            # change log
2980d24de9dSSimon Glass            elif line == '---':
2990d24de9dSSimon Glass                self.state = STATE_DIFFS
3000d24de9dSSimon Glass
3010d24de9dSSimon Glass                # Output the tags (signeoff first), then change list
3020d24de9dSSimon Glass                out = []
3030d24de9dSSimon Glass                log = self.series.MakeChangeLog(self.commit)
304e752edcbSSimon Glass                out += [line]
305e752edcbSSimon Glass                if self.commit:
306e752edcbSSimon Glass                    out += self.commit.notes
307e752edcbSSimon Glass                out += [''] + log
3080d24de9dSSimon Glass            elif self.found_test:
3090d24de9dSSimon Glass                if not re_allowed_after_test.match(line):
3100d24de9dSSimon Glass                    self.lines_after_test += 1
3110d24de9dSSimon Glass
3120d24de9dSSimon Glass        return out
3130d24de9dSSimon Glass
3140d24de9dSSimon Glass    def Finalize(self):
3150d24de9dSSimon Glass        """Close out processing of this patch stream"""
3160d24de9dSSimon Glass        self.CloseCommit()
3170d24de9dSSimon Glass        if self.lines_after_test:
3180d24de9dSSimon Glass            self.warn.append('Found %d lines after TEST=' %
3190d24de9dSSimon Glass                    self.lines_after_test)
3200d24de9dSSimon Glass
3210d24de9dSSimon Glass    def ProcessStream(self, infd, outfd):
3220d24de9dSSimon Glass        """Copy a stream from infd to outfd, filtering out unwanting things.
3230d24de9dSSimon Glass
3240d24de9dSSimon Glass        This is used to process patch files one at a time.
3250d24de9dSSimon Glass
3260d24de9dSSimon Glass        Args:
3270d24de9dSSimon Glass            infd: Input stream file object
3280d24de9dSSimon Glass            outfd: Output stream file object
3290d24de9dSSimon Glass        """
3300d24de9dSSimon Glass        # Extract the filename from each diff, for nice warnings
3310d24de9dSSimon Glass        fname = None
3320d24de9dSSimon Glass        last_fname = None
3330d24de9dSSimon Glass        re_fname = re.compile('diff --git a/(.*) b/.*')
3340d24de9dSSimon Glass        while True:
3350d24de9dSSimon Glass            line = infd.readline()
3360d24de9dSSimon Glass            if not line:
3370d24de9dSSimon Glass                break
3380d24de9dSSimon Glass            out = self.ProcessLine(line)
3390d24de9dSSimon Glass
3400d24de9dSSimon Glass            # Try to detect blank lines at EOF
3410d24de9dSSimon Glass            for line in out:
3420d24de9dSSimon Glass                match = re_fname.match(line)
3430d24de9dSSimon Glass                if match:
3440d24de9dSSimon Glass                    last_fname = fname
3450d24de9dSSimon Glass                    fname = match.group(1)
3460d24de9dSSimon Glass                if line == '+':
3470d24de9dSSimon Glass                    self.blank_count += 1
3480d24de9dSSimon Glass                else:
3490d24de9dSSimon Glass                    if self.blank_count and (line == '-- ' or match):
3500d24de9dSSimon Glass                        self.warn.append("Found possible blank line(s) at "
3510d24de9dSSimon Glass                                "end of file '%s'" % last_fname)
3520d24de9dSSimon Glass                    outfd.write('+\n' * self.blank_count)
3530d24de9dSSimon Glass                    outfd.write(line + '\n')
3540d24de9dSSimon Glass                    self.blank_count = 0
3550d24de9dSSimon Glass        self.Finalize()
3560d24de9dSSimon Glass
3570d24de9dSSimon Glass
358e62f905eSSimon Glassdef GetMetaDataForList(commit_range, git_dir=None, count=None,
359950a2313SSimon Glass                       series = None, allow_overwrite=False):
360e62f905eSSimon Glass    """Reads out patch series metadata from the commits
361e62f905eSSimon Glass
362e62f905eSSimon Glass    This does a 'git log' on the relevant commits and pulls out the tags we
363e62f905eSSimon Glass    are interested in.
364e62f905eSSimon Glass
365e62f905eSSimon Glass    Args:
366e62f905eSSimon Glass        commit_range: Range of commits to count (e.g. 'HEAD..base')
367e62f905eSSimon Glass        git_dir: Path to git repositiory (None to use default)
368e62f905eSSimon Glass        count: Number of commits to list, or None for no limit
369e62f905eSSimon Glass        series: Series object to add information into. By default a new series
370e62f905eSSimon Glass            is started.
371950a2313SSimon Glass        allow_overwrite: Allow tags to overwrite an existing tag
372e62f905eSSimon Glass    Returns:
373e62f905eSSimon Glass        A Series object containing information about the commits.
374e62f905eSSimon Glass    """
375891b7a07SSimon Glass    if not series:
376891b7a07SSimon Glass        series = Series()
377950a2313SSimon Glass    series.allow_overwrite = allow_overwrite
378cda2a611SSimon Glass    params = gitutil.LogCmd(commit_range,reverse=True, count=count,
379cda2a611SSimon Glass                            git_dir=git_dir)
380cda2a611SSimon Glass    stdout = command.RunPipe([params], capture=True).stdout
381e62f905eSSimon Glass    ps = PatchStream(series, is_log=True)
382e62f905eSSimon Glass    for line in stdout.splitlines():
383e62f905eSSimon Glass        ps.ProcessLine(line)
384e62f905eSSimon Glass    ps.Finalize()
385e62f905eSSimon Glass    return series
386e62f905eSSimon Glass
3870d24de9dSSimon Glassdef GetMetaData(start, count):
3880d24de9dSSimon Glass    """Reads out patch series metadata from the commits
3890d24de9dSSimon Glass
3900d24de9dSSimon Glass    This does a 'git log' on the relevant commits and pulls out the tags we
3910d24de9dSSimon Glass    are interested in.
3920d24de9dSSimon Glass
3930d24de9dSSimon Glass    Args:
3940d24de9dSSimon Glass        start: Commit to start from: 0=HEAD, 1=next one, etc.
3950d24de9dSSimon Glass        count: Number of commits to list
3960d24de9dSSimon Glass    """
397e62f905eSSimon Glass    return GetMetaDataForList('HEAD~%d' % start, None, count)
3980d24de9dSSimon Glass
3990d24de9dSSimon Glassdef FixPatch(backup_dir, fname, series, commit):
4000d24de9dSSimon Glass    """Fix up a patch file, by adding/removing as required.
4010d24de9dSSimon Glass
4020d24de9dSSimon Glass    We remove our tags from the patch file, insert changes lists, etc.
4030d24de9dSSimon Glass    The patch file is processed in place, and overwritten.
4040d24de9dSSimon Glass
4050d24de9dSSimon Glass    A backup file is put into backup_dir (if not None).
4060d24de9dSSimon Glass
4070d24de9dSSimon Glass    Args:
4080d24de9dSSimon Glass        fname: Filename to patch file to process
4090d24de9dSSimon Glass        series: Series information about this patch set
4100d24de9dSSimon Glass        commit: Commit object for this patch file
4110d24de9dSSimon Glass    Return:
4120d24de9dSSimon Glass        A list of errors, or [] if all ok.
4130d24de9dSSimon Glass    """
4140d24de9dSSimon Glass    handle, tmpname = tempfile.mkstemp()
4150d24de9dSSimon Glass    outfd = os.fdopen(handle, 'w')
4160d24de9dSSimon Glass    infd = open(fname, 'r')
4170d24de9dSSimon Glass    ps = PatchStream(series)
4180d24de9dSSimon Glass    ps.commit = commit
4190d24de9dSSimon Glass    ps.ProcessStream(infd, outfd)
4200d24de9dSSimon Glass    infd.close()
4210d24de9dSSimon Glass    outfd.close()
4220d24de9dSSimon Glass
4230d24de9dSSimon Glass    # Create a backup file if required
4240d24de9dSSimon Glass    if backup_dir:
4250d24de9dSSimon Glass        shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
4260d24de9dSSimon Glass    shutil.move(tmpname, fname)
4270d24de9dSSimon Glass    return ps.warn
4280d24de9dSSimon Glass
4290d24de9dSSimon Glassdef FixPatches(series, fnames):
4300d24de9dSSimon Glass    """Fix up a list of patches identified by filenames
4310d24de9dSSimon Glass
4320d24de9dSSimon Glass    The patch files are processed in place, and overwritten.
4330d24de9dSSimon Glass
4340d24de9dSSimon Glass    Args:
4350d24de9dSSimon Glass        series: The series object
4360d24de9dSSimon Glass        fnames: List of patch files to process
4370d24de9dSSimon Glass    """
4380d24de9dSSimon Glass    # Current workflow creates patches, so we shouldn't need a backup
4390d24de9dSSimon Glass    backup_dir = None  #tempfile.mkdtemp('clean-patch')
4400d24de9dSSimon Glass    count = 0
4410d24de9dSSimon Glass    for fname in fnames:
4420d24de9dSSimon Glass        commit = series.commits[count]
4430d24de9dSSimon Glass        commit.patch = fname
4440d24de9dSSimon Glass        result = FixPatch(backup_dir, fname, series, commit)
4450d24de9dSSimon Glass        if result:
4460d24de9dSSimon Glass            print '%d warnings for %s:' % (len(result), fname)
4470d24de9dSSimon Glass            for warn in result:
4480d24de9dSSimon Glass                print '\t', warn
4490d24de9dSSimon Glass            print
4500d24de9dSSimon Glass        count += 1
4510d24de9dSSimon Glass    print 'Cleaned %d patches' % count
4520d24de9dSSimon Glass    return series
4530d24de9dSSimon Glass
4540d24de9dSSimon Glassdef InsertCoverLetter(fname, series, count):
4550d24de9dSSimon Glass    """Inserts a cover letter with the required info into patch 0
4560d24de9dSSimon Glass
4570d24de9dSSimon Glass    Args:
4580d24de9dSSimon Glass        fname: Input / output filename of the cover letter file
4590d24de9dSSimon Glass        series: Series object
4600d24de9dSSimon Glass        count: Number of patches in the series
4610d24de9dSSimon Glass    """
4620d24de9dSSimon Glass    fd = open(fname, 'r')
4630d24de9dSSimon Glass    lines = fd.readlines()
4640d24de9dSSimon Glass    fd.close()
4650d24de9dSSimon Glass
4660d24de9dSSimon Glass    fd = open(fname, 'w')
4670d24de9dSSimon Glass    text = series.cover
4680d24de9dSSimon Glass    prefix = series.GetPatchPrefix()
4690d24de9dSSimon Glass    for line in lines:
4700d24de9dSSimon Glass        if line.startswith('Subject:'):
4710d24de9dSSimon Glass            # TODO: if more than 10 patches this should save 00/xx, not 0/xx
4720d24de9dSSimon Glass            line = 'Subject: [%s 0/%d] %s\n' % (prefix, count, text[0])
4730d24de9dSSimon Glass
4740d24de9dSSimon Glass        # Insert our cover letter
4750d24de9dSSimon Glass        elif line.startswith('*** BLURB HERE ***'):
4760d24de9dSSimon Glass            # First the blurb test
4770d24de9dSSimon Glass            line = '\n'.join(text[1:]) + '\n'
4780d24de9dSSimon Glass            if series.get('notes'):
4790d24de9dSSimon Glass                line += '\n'.join(series.notes) + '\n'
4800d24de9dSSimon Glass
4810d24de9dSSimon Glass            # Now the change list
4820d24de9dSSimon Glass            out = series.MakeChangeLog(None)
4830d24de9dSSimon Glass            line += '\n' + '\n'.join(out)
4840d24de9dSSimon Glass        fd.write(line)
4850d24de9dSSimon Glass    fd.close()
486