xref: /rk3399_rockchip-uboot/tools/patman/patchstream.py (revision 0d24de9d558209fa525f350d1320c343f241c3f5)
1*0d24de9dSSimon Glass# Copyright (c) 2011 The Chromium OS Authors.
2*0d24de9dSSimon Glass#
3*0d24de9dSSimon Glass# See file CREDITS for list of people who contributed to this
4*0d24de9dSSimon Glass# project.
5*0d24de9dSSimon Glass#
6*0d24de9dSSimon Glass# This program is free software; you can redistribute it and/or
7*0d24de9dSSimon Glass# modify it under the terms of the GNU General Public License as
8*0d24de9dSSimon Glass# published by the Free Software Foundation; either version 2 of
9*0d24de9dSSimon Glass# the License, or (at your option) any later version.
10*0d24de9dSSimon Glass#
11*0d24de9dSSimon Glass# This program is distributed in the hope that it will be useful,
12*0d24de9dSSimon Glass# but WITHOUT ANY WARRANTY; without even the implied warranty of
13*0d24de9dSSimon Glass# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14*0d24de9dSSimon Glass# GNU General Public License for more details.
15*0d24de9dSSimon Glass#
16*0d24de9dSSimon Glass# You should have received a copy of the GNU General Public License
17*0d24de9dSSimon Glass# along with this program; if not, write to the Free Software
18*0d24de9dSSimon Glass# Foundation, Inc., 59 Temple Place, Suite 330, Boston,
19*0d24de9dSSimon Glass# MA 02111-1307 USA
20*0d24de9dSSimon Glass#
21*0d24de9dSSimon Glass
22*0d24de9dSSimon Glassimport os
23*0d24de9dSSimon Glassimport re
24*0d24de9dSSimon Glassimport shutil
25*0d24de9dSSimon Glassimport tempfile
26*0d24de9dSSimon Glass
27*0d24de9dSSimon Glassimport command
28*0d24de9dSSimon Glassimport commit
29*0d24de9dSSimon Glassimport gitutil
30*0d24de9dSSimon Glassfrom series import Series
31*0d24de9dSSimon Glass
32*0d24de9dSSimon Glass# Tags that we detect and remove
33*0d24de9dSSimon Glassre_remove = re.compile('^BUG=|^TEST=|^Change-Id:|^Review URL:'
34*0d24de9dSSimon Glass    '|Reviewed-on:|Reviewed-by:')
35*0d24de9dSSimon Glass
36*0d24de9dSSimon Glass# Lines which are allowed after a TEST= line
37*0d24de9dSSimon Glassre_allowed_after_test = re.compile('^Signed-off-by:')
38*0d24de9dSSimon Glass
39*0d24de9dSSimon Glass# The start of the cover letter
40*0d24de9dSSimon Glassre_cover = re.compile('^Cover-letter:')
41*0d24de9dSSimon Glass
42*0d24de9dSSimon Glass# Patch series tag
43*0d24de9dSSimon Glassre_series = re.compile('^Series-(\w*): *(.*)')
44*0d24de9dSSimon Glass
45*0d24de9dSSimon Glass# Commit tags that we want to collect and keep
46*0d24de9dSSimon Glassre_tag = re.compile('^(Tested-by|Acked-by|Signed-off-by|Cc): (.*)')
47*0d24de9dSSimon Glass
48*0d24de9dSSimon Glass# The start of a new commit in the git log
49*0d24de9dSSimon Glassre_commit = re.compile('^commit (.*)')
50*0d24de9dSSimon Glass
51*0d24de9dSSimon Glass# We detect these since checkpatch doesn't always do it
52*0d24de9dSSimon Glassre_space_before_tab = re.compile('^[+].* \t')
53*0d24de9dSSimon Glass
54*0d24de9dSSimon Glass# States we can be in - can we use range() and still have comments?
55*0d24de9dSSimon GlassSTATE_MSG_HEADER = 0        # Still in the message header
56*0d24de9dSSimon GlassSTATE_PATCH_SUBJECT = 1     # In patch subject (first line of log for a commit)
57*0d24de9dSSimon GlassSTATE_PATCH_HEADER = 2      # In patch header (after the subject)
58*0d24de9dSSimon GlassSTATE_DIFFS = 3             # In the diff part (past --- line)
59*0d24de9dSSimon Glass
60*0d24de9dSSimon Glassclass PatchStream:
61*0d24de9dSSimon Glass    """Class for detecting/injecting tags in a patch or series of patches
62*0d24de9dSSimon Glass
63*0d24de9dSSimon Glass    We support processing the output of 'git log' to read out the tags we
64*0d24de9dSSimon Glass    are interested in. We can also process a patch file in order to remove
65*0d24de9dSSimon Glass    unwanted tags or inject additional ones. These correspond to the two
66*0d24de9dSSimon Glass    phases of processing.
67*0d24de9dSSimon Glass    """
68*0d24de9dSSimon Glass    def __init__(self, series, name=None, is_log=False):
69*0d24de9dSSimon Glass        self.skip_blank = False          # True to skip a single blank line
70*0d24de9dSSimon Glass        self.found_test = False          # Found a TEST= line
71*0d24de9dSSimon Glass        self.lines_after_test = 0        # MNumber of lines found after TEST=
72*0d24de9dSSimon Glass        self.warn = []                   # List of warnings we have collected
73*0d24de9dSSimon Glass        self.linenum = 1                 # Output line number we are up to
74*0d24de9dSSimon Glass        self.in_section = None           # Name of start...END section we are in
75*0d24de9dSSimon Glass        self.notes = []                  # Series notes
76*0d24de9dSSimon Glass        self.section = []                # The current section...END section
77*0d24de9dSSimon Glass        self.series = series             # Info about the patch series
78*0d24de9dSSimon Glass        self.is_log = is_log             # True if indent like git log
79*0d24de9dSSimon Glass        self.in_change = 0               # Non-zero if we are in a change list
80*0d24de9dSSimon Glass        self.blank_count = 0             # Number of blank lines stored up
81*0d24de9dSSimon Glass        self.state = STATE_MSG_HEADER    # What state are we in?
82*0d24de9dSSimon Glass        self.tags = []                   # Tags collected, like Tested-by...
83*0d24de9dSSimon Glass        self.signoff = []                # Contents of signoff line
84*0d24de9dSSimon Glass        self.commit = None               # Current commit
85*0d24de9dSSimon Glass
86*0d24de9dSSimon Glass    def AddToSeries(self, line, name, value):
87*0d24de9dSSimon Glass        """Add a new Series-xxx tag.
88*0d24de9dSSimon Glass
89*0d24de9dSSimon Glass        When a Series-xxx tag is detected, we come here to record it, if we
90*0d24de9dSSimon Glass        are scanning a 'git log'.
91*0d24de9dSSimon Glass
92*0d24de9dSSimon Glass        Args:
93*0d24de9dSSimon Glass            line: Source line containing tag (useful for debug/error messages)
94*0d24de9dSSimon Glass            name: Tag name (part after 'Series-')
95*0d24de9dSSimon Glass            value: Tag value (part after 'Series-xxx: ')
96*0d24de9dSSimon Glass        """
97*0d24de9dSSimon Glass        if name == 'notes':
98*0d24de9dSSimon Glass            self.in_section = name
99*0d24de9dSSimon Glass            self.skip_blank = False
100*0d24de9dSSimon Glass        if self.is_log:
101*0d24de9dSSimon Glass            self.series.AddTag(self.commit, line, name, value)
102*0d24de9dSSimon Glass
103*0d24de9dSSimon Glass    def CloseCommit(self):
104*0d24de9dSSimon Glass        """Save the current commit into our commit list, and reset our state"""
105*0d24de9dSSimon Glass        if self.commit and self.is_log:
106*0d24de9dSSimon Glass            self.series.AddCommit(self.commit)
107*0d24de9dSSimon Glass            self.commit = None
108*0d24de9dSSimon Glass
109*0d24de9dSSimon Glass    def FormatTags(self, tags):
110*0d24de9dSSimon Glass        out_list = []
111*0d24de9dSSimon Glass        for tag in sorted(tags):
112*0d24de9dSSimon Glass            if tag.startswith('Cc:'):
113*0d24de9dSSimon Glass                tag_list = tag[4:].split(',')
114*0d24de9dSSimon Glass                out_list += gitutil.BuildEmailList(tag_list, 'Cc:')
115*0d24de9dSSimon Glass            else:
116*0d24de9dSSimon Glass                out_list.append(tag)
117*0d24de9dSSimon Glass        return out_list
118*0d24de9dSSimon Glass
119*0d24de9dSSimon Glass    def ProcessLine(self, line):
120*0d24de9dSSimon Glass        """Process a single line of a patch file or commit log
121*0d24de9dSSimon Glass
122*0d24de9dSSimon Glass        This process a line and returns a list of lines to output. The list
123*0d24de9dSSimon Glass        may be empty or may contain multiple output lines.
124*0d24de9dSSimon Glass
125*0d24de9dSSimon Glass        This is where all the complicated logic is located. The class's
126*0d24de9dSSimon Glass        state is used to move between different states and detect things
127*0d24de9dSSimon Glass        properly.
128*0d24de9dSSimon Glass
129*0d24de9dSSimon Glass        We can be in one of two modes:
130*0d24de9dSSimon Glass            self.is_log == True: This is 'git log' mode, where most output is
131*0d24de9dSSimon Glass                indented by 4 characters and we are scanning for tags
132*0d24de9dSSimon Glass
133*0d24de9dSSimon Glass            self.is_log == False: This is 'patch' mode, where we already have
134*0d24de9dSSimon Glass                all the tags, and are processing patches to remove junk we
135*0d24de9dSSimon Glass                don't want, and add things we think are required.
136*0d24de9dSSimon Glass
137*0d24de9dSSimon Glass        Args:
138*0d24de9dSSimon Glass            line: text line to process
139*0d24de9dSSimon Glass
140*0d24de9dSSimon Glass        Returns:
141*0d24de9dSSimon Glass            list of output lines, or [] if nothing should be output
142*0d24de9dSSimon Glass        """
143*0d24de9dSSimon Glass        # Initially we have no output. Prepare the input line string
144*0d24de9dSSimon Glass        out = []
145*0d24de9dSSimon Glass        line = line.rstrip('\n')
146*0d24de9dSSimon Glass        if self.is_log:
147*0d24de9dSSimon Glass            if line[:4] == '    ':
148*0d24de9dSSimon Glass                line = line[4:]
149*0d24de9dSSimon Glass
150*0d24de9dSSimon Glass        # Handle state transition and skipping blank lines
151*0d24de9dSSimon Glass        series_match = re_series.match(line)
152*0d24de9dSSimon Glass        commit_match = re_commit.match(line) if self.is_log else None
153*0d24de9dSSimon Glass        tag_match = None
154*0d24de9dSSimon Glass        if self.state == STATE_PATCH_HEADER:
155*0d24de9dSSimon Glass            tag_match = re_tag.match(line)
156*0d24de9dSSimon Glass        is_blank = not line.strip()
157*0d24de9dSSimon Glass        if is_blank:
158*0d24de9dSSimon Glass            if (self.state == STATE_MSG_HEADER
159*0d24de9dSSimon Glass                    or self.state == STATE_PATCH_SUBJECT):
160*0d24de9dSSimon Glass                self.state += 1
161*0d24de9dSSimon Glass
162*0d24de9dSSimon Glass            # We don't have a subject in the text stream of patch files
163*0d24de9dSSimon Glass            # It has its own line with a Subject: tag
164*0d24de9dSSimon Glass            if not self.is_log and self.state == STATE_PATCH_SUBJECT:
165*0d24de9dSSimon Glass                self.state += 1
166*0d24de9dSSimon Glass        elif commit_match:
167*0d24de9dSSimon Glass            self.state = STATE_MSG_HEADER
168*0d24de9dSSimon Glass
169*0d24de9dSSimon Glass        # If we are in a section, keep collecting lines until we see END
170*0d24de9dSSimon Glass        if self.in_section:
171*0d24de9dSSimon Glass            if line == 'END':
172*0d24de9dSSimon Glass                if self.in_section == 'cover':
173*0d24de9dSSimon Glass                    self.series.cover = self.section
174*0d24de9dSSimon Glass                elif self.in_section == 'notes':
175*0d24de9dSSimon Glass                    if self.is_log:
176*0d24de9dSSimon Glass                        self.series.notes += self.section
177*0d24de9dSSimon Glass                else:
178*0d24de9dSSimon Glass                    self.warn.append("Unknown section '%s'" % self.in_section)
179*0d24de9dSSimon Glass                self.in_section = None
180*0d24de9dSSimon Glass                self.skip_blank = True
181*0d24de9dSSimon Glass                self.section = []
182*0d24de9dSSimon Glass            else:
183*0d24de9dSSimon Glass                self.section.append(line)
184*0d24de9dSSimon Glass
185*0d24de9dSSimon Glass        # Detect the commit subject
186*0d24de9dSSimon Glass        elif not is_blank and self.state == STATE_PATCH_SUBJECT:
187*0d24de9dSSimon Glass            self.commit.subject = line
188*0d24de9dSSimon Glass
189*0d24de9dSSimon Glass        # Detect the tags we want to remove, and skip blank lines
190*0d24de9dSSimon Glass        elif re_remove.match(line):
191*0d24de9dSSimon Glass            self.skip_blank = True
192*0d24de9dSSimon Glass
193*0d24de9dSSimon Glass            # TEST= should be the last thing in the commit, so remove
194*0d24de9dSSimon Glass            # everything after it
195*0d24de9dSSimon Glass            if line.startswith('TEST='):
196*0d24de9dSSimon Glass                self.found_test = True
197*0d24de9dSSimon Glass        elif self.skip_blank and is_blank:
198*0d24de9dSSimon Glass            self.skip_blank = False
199*0d24de9dSSimon Glass
200*0d24de9dSSimon Glass        # Detect the start of a cover letter section
201*0d24de9dSSimon Glass        elif re_cover.match(line):
202*0d24de9dSSimon Glass            self.in_section = 'cover'
203*0d24de9dSSimon Glass            self.skip_blank = False
204*0d24de9dSSimon Glass
205*0d24de9dSSimon Glass        # If we are in a change list, key collected lines until a blank one
206*0d24de9dSSimon Glass        elif self.in_change:
207*0d24de9dSSimon Glass            if is_blank:
208*0d24de9dSSimon Glass                # Blank line ends this change list
209*0d24de9dSSimon Glass                self.in_change = 0
210*0d24de9dSSimon Glass            else:
211*0d24de9dSSimon Glass                self.series.AddChange(self.in_change, self.commit, line)
212*0d24de9dSSimon Glass            self.skip_blank = False
213*0d24de9dSSimon Glass
214*0d24de9dSSimon Glass        # Detect Series-xxx tags
215*0d24de9dSSimon Glass        elif series_match:
216*0d24de9dSSimon Glass            name = series_match.group(1)
217*0d24de9dSSimon Glass            value = series_match.group(2)
218*0d24de9dSSimon Glass            if name == 'changes':
219*0d24de9dSSimon Glass                # value is the version number: e.g. 1, or 2
220*0d24de9dSSimon Glass                try:
221*0d24de9dSSimon Glass                    value = int(value)
222*0d24de9dSSimon Glass                except ValueError as str:
223*0d24de9dSSimon Glass                    raise ValueError("%s: Cannot decode version info '%s'" %
224*0d24de9dSSimon Glass                        (self.commit.hash, line))
225*0d24de9dSSimon Glass                self.in_change = int(value)
226*0d24de9dSSimon Glass            else:
227*0d24de9dSSimon Glass                self.AddToSeries(line, name, value)
228*0d24de9dSSimon Glass                self.skip_blank = True
229*0d24de9dSSimon Glass
230*0d24de9dSSimon Glass        # Detect the start of a new commit
231*0d24de9dSSimon Glass        elif commit_match:
232*0d24de9dSSimon Glass            self.CloseCommit()
233*0d24de9dSSimon Glass            self.commit = commit.Commit(commit_match.group(1)[:7])
234*0d24de9dSSimon Glass
235*0d24de9dSSimon Glass        # Detect tags in the commit message
236*0d24de9dSSimon Glass        elif tag_match:
237*0d24de9dSSimon Glass            # Onlly allow a single signoff tag
238*0d24de9dSSimon Glass            if tag_match.group(1) == 'Signed-off-by':
239*0d24de9dSSimon Glass                if self.signoff:
240*0d24de9dSSimon Glass                    self.warn.append('Patch has more than one Signed-off-by '
241*0d24de9dSSimon Glass                            'tag')
242*0d24de9dSSimon Glass                self.signoff += [line]
243*0d24de9dSSimon Glass
244*0d24de9dSSimon Glass            # Remove Tested-by self, since few will take much notice
245*0d24de9dSSimon Glass            elif (tag_match.group(1) == 'Tested-by' and
246*0d24de9dSSimon Glass                    tag_match.group(2).find(os.getenv('USER') + '@') != -1):
247*0d24de9dSSimon Glass                self.warn.append("Ignoring %s" % line)
248*0d24de9dSSimon Glass            elif tag_match.group(1) == 'Cc':
249*0d24de9dSSimon Glass                self.commit.AddCc(tag_match.group(2).split(','))
250*0d24de9dSSimon Glass            else:
251*0d24de9dSSimon Glass                self.tags.append(line);
252*0d24de9dSSimon Glass
253*0d24de9dSSimon Glass        # Well that means this is an ordinary line
254*0d24de9dSSimon Glass        else:
255*0d24de9dSSimon Glass            pos = 1
256*0d24de9dSSimon Glass            # Look for ugly ASCII characters
257*0d24de9dSSimon Glass            for ch in line:
258*0d24de9dSSimon Glass                # TODO: Would be nicer to report source filename and line
259*0d24de9dSSimon Glass                if ord(ch) > 0x80:
260*0d24de9dSSimon Glass                    self.warn.append("Line %d/%d ('%s') has funny ascii char" %
261*0d24de9dSSimon Glass                        (self.linenum, pos, line))
262*0d24de9dSSimon Glass                pos += 1
263*0d24de9dSSimon Glass
264*0d24de9dSSimon Glass            # Look for space before tab
265*0d24de9dSSimon Glass            m = re_space_before_tab.match(line)
266*0d24de9dSSimon Glass            if m:
267*0d24de9dSSimon Glass                self.warn.append('Line %d/%d has space before tab' %
268*0d24de9dSSimon Glass                    (self.linenum, m.start()))
269*0d24de9dSSimon Glass
270*0d24de9dSSimon Glass            # OK, we have a valid non-blank line
271*0d24de9dSSimon Glass            out = [line]
272*0d24de9dSSimon Glass            self.linenum += 1
273*0d24de9dSSimon Glass            self.skip_blank = False
274*0d24de9dSSimon Glass            if self.state == STATE_DIFFS:
275*0d24de9dSSimon Glass                pass
276*0d24de9dSSimon Glass
277*0d24de9dSSimon Glass            # If this is the start of the diffs section, emit our tags and
278*0d24de9dSSimon Glass            # change log
279*0d24de9dSSimon Glass            elif line == '---':
280*0d24de9dSSimon Glass                self.state = STATE_DIFFS
281*0d24de9dSSimon Glass
282*0d24de9dSSimon Glass                # Output the tags (signeoff first), then change list
283*0d24de9dSSimon Glass                out = []
284*0d24de9dSSimon Glass                if self.signoff:
285*0d24de9dSSimon Glass                    out += self.signoff
286*0d24de9dSSimon Glass                log = self.series.MakeChangeLog(self.commit)
287*0d24de9dSSimon Glass                out += self.FormatTags(self.tags)
288*0d24de9dSSimon Glass                out += [line] + log
289*0d24de9dSSimon Glass            elif self.found_test:
290*0d24de9dSSimon Glass                if not re_allowed_after_test.match(line):
291*0d24de9dSSimon Glass                    self.lines_after_test += 1
292*0d24de9dSSimon Glass
293*0d24de9dSSimon Glass        return out
294*0d24de9dSSimon Glass
295*0d24de9dSSimon Glass    def Finalize(self):
296*0d24de9dSSimon Glass        """Close out processing of this patch stream"""
297*0d24de9dSSimon Glass        self.CloseCommit()
298*0d24de9dSSimon Glass        if self.lines_after_test:
299*0d24de9dSSimon Glass            self.warn.append('Found %d lines after TEST=' %
300*0d24de9dSSimon Glass                    self.lines_after_test)
301*0d24de9dSSimon Glass
302*0d24de9dSSimon Glass    def ProcessStream(self, infd, outfd):
303*0d24de9dSSimon Glass        """Copy a stream from infd to outfd, filtering out unwanting things.
304*0d24de9dSSimon Glass
305*0d24de9dSSimon Glass        This is used to process patch files one at a time.
306*0d24de9dSSimon Glass
307*0d24de9dSSimon Glass        Args:
308*0d24de9dSSimon Glass            infd: Input stream file object
309*0d24de9dSSimon Glass            outfd: Output stream file object
310*0d24de9dSSimon Glass        """
311*0d24de9dSSimon Glass        # Extract the filename from each diff, for nice warnings
312*0d24de9dSSimon Glass        fname = None
313*0d24de9dSSimon Glass        last_fname = None
314*0d24de9dSSimon Glass        re_fname = re.compile('diff --git a/(.*) b/.*')
315*0d24de9dSSimon Glass        while True:
316*0d24de9dSSimon Glass            line = infd.readline()
317*0d24de9dSSimon Glass            if not line:
318*0d24de9dSSimon Glass                break
319*0d24de9dSSimon Glass            out = self.ProcessLine(line)
320*0d24de9dSSimon Glass
321*0d24de9dSSimon Glass            # Try to detect blank lines at EOF
322*0d24de9dSSimon Glass            for line in out:
323*0d24de9dSSimon Glass                match = re_fname.match(line)
324*0d24de9dSSimon Glass                if match:
325*0d24de9dSSimon Glass                    last_fname = fname
326*0d24de9dSSimon Glass                    fname = match.group(1)
327*0d24de9dSSimon Glass                if line == '+':
328*0d24de9dSSimon Glass                    self.blank_count += 1
329*0d24de9dSSimon Glass                else:
330*0d24de9dSSimon Glass                    if self.blank_count and (line == '-- ' or match):
331*0d24de9dSSimon Glass                        self.warn.append("Found possible blank line(s) at "
332*0d24de9dSSimon Glass                                "end of file '%s'" % last_fname)
333*0d24de9dSSimon Glass                    outfd.write('+\n' * self.blank_count)
334*0d24de9dSSimon Glass                    outfd.write(line + '\n')
335*0d24de9dSSimon Glass                    self.blank_count = 0
336*0d24de9dSSimon Glass        self.Finalize()
337*0d24de9dSSimon Glass
338*0d24de9dSSimon Glass
339*0d24de9dSSimon Glassdef GetMetaData(start, count):
340*0d24de9dSSimon Glass    """Reads out patch series metadata from the commits
341*0d24de9dSSimon Glass
342*0d24de9dSSimon Glass    This does a 'git log' on the relevant commits and pulls out the tags we
343*0d24de9dSSimon Glass    are interested in.
344*0d24de9dSSimon Glass
345*0d24de9dSSimon Glass    Args:
346*0d24de9dSSimon Glass        start: Commit to start from: 0=HEAD, 1=next one, etc.
347*0d24de9dSSimon Glass        count: Number of commits to list
348*0d24de9dSSimon Glass    """
349*0d24de9dSSimon Glass    pipe = [['git', 'log', '--reverse', 'HEAD~%d' % start, '-n%d' % count]]
350*0d24de9dSSimon Glass    stdout = command.RunPipe(pipe, capture=True)
351*0d24de9dSSimon Glass    series = Series()
352*0d24de9dSSimon Glass    ps = PatchStream(series, is_log=True)
353*0d24de9dSSimon Glass    for line in stdout.splitlines():
354*0d24de9dSSimon Glass        ps.ProcessLine(line)
355*0d24de9dSSimon Glass    ps.Finalize()
356*0d24de9dSSimon Glass    return series
357*0d24de9dSSimon Glass
358*0d24de9dSSimon Glassdef FixPatch(backup_dir, fname, series, commit):
359*0d24de9dSSimon Glass    """Fix up a patch file, by adding/removing as required.
360*0d24de9dSSimon Glass
361*0d24de9dSSimon Glass    We remove our tags from the patch file, insert changes lists, etc.
362*0d24de9dSSimon Glass    The patch file is processed in place, and overwritten.
363*0d24de9dSSimon Glass
364*0d24de9dSSimon Glass    A backup file is put into backup_dir (if not None).
365*0d24de9dSSimon Glass
366*0d24de9dSSimon Glass    Args:
367*0d24de9dSSimon Glass        fname: Filename to patch file to process
368*0d24de9dSSimon Glass        series: Series information about this patch set
369*0d24de9dSSimon Glass        commit: Commit object for this patch file
370*0d24de9dSSimon Glass    Return:
371*0d24de9dSSimon Glass        A list of errors, or [] if all ok.
372*0d24de9dSSimon Glass    """
373*0d24de9dSSimon Glass    handle, tmpname = tempfile.mkstemp()
374*0d24de9dSSimon Glass    outfd = os.fdopen(handle, 'w')
375*0d24de9dSSimon Glass    infd = open(fname, 'r')
376*0d24de9dSSimon Glass    ps = PatchStream(series)
377*0d24de9dSSimon Glass    ps.commit = commit
378*0d24de9dSSimon Glass    ps.ProcessStream(infd, outfd)
379*0d24de9dSSimon Glass    infd.close()
380*0d24de9dSSimon Glass    outfd.close()
381*0d24de9dSSimon Glass
382*0d24de9dSSimon Glass    # Create a backup file if required
383*0d24de9dSSimon Glass    if backup_dir:
384*0d24de9dSSimon Glass        shutil.copy(fname, os.path.join(backup_dir, os.path.basename(fname)))
385*0d24de9dSSimon Glass    shutil.move(tmpname, fname)
386*0d24de9dSSimon Glass    return ps.warn
387*0d24de9dSSimon Glass
388*0d24de9dSSimon Glassdef FixPatches(series, fnames):
389*0d24de9dSSimon Glass    """Fix up a list of patches identified by filenames
390*0d24de9dSSimon Glass
391*0d24de9dSSimon Glass    The patch files are processed in place, and overwritten.
392*0d24de9dSSimon Glass
393*0d24de9dSSimon Glass    Args:
394*0d24de9dSSimon Glass        series: The series object
395*0d24de9dSSimon Glass        fnames: List of patch files to process
396*0d24de9dSSimon Glass    """
397*0d24de9dSSimon Glass    # Current workflow creates patches, so we shouldn't need a backup
398*0d24de9dSSimon Glass    backup_dir = None  #tempfile.mkdtemp('clean-patch')
399*0d24de9dSSimon Glass    count = 0
400*0d24de9dSSimon Glass    for fname in fnames:
401*0d24de9dSSimon Glass        commit = series.commits[count]
402*0d24de9dSSimon Glass        commit.patch = fname
403*0d24de9dSSimon Glass        result = FixPatch(backup_dir, fname, series, commit)
404*0d24de9dSSimon Glass        if result:
405*0d24de9dSSimon Glass            print '%d warnings for %s:' % (len(result), fname)
406*0d24de9dSSimon Glass            for warn in result:
407*0d24de9dSSimon Glass                print '\t', warn
408*0d24de9dSSimon Glass            print
409*0d24de9dSSimon Glass        count += 1
410*0d24de9dSSimon Glass    print 'Cleaned %d patches' % count
411*0d24de9dSSimon Glass    return series
412*0d24de9dSSimon Glass
413*0d24de9dSSimon Glassdef InsertCoverLetter(fname, series, count):
414*0d24de9dSSimon Glass    """Inserts a cover letter with the required info into patch 0
415*0d24de9dSSimon Glass
416*0d24de9dSSimon Glass    Args:
417*0d24de9dSSimon Glass        fname: Input / output filename of the cover letter file
418*0d24de9dSSimon Glass        series: Series object
419*0d24de9dSSimon Glass        count: Number of patches in the series
420*0d24de9dSSimon Glass    """
421*0d24de9dSSimon Glass    fd = open(fname, 'r')
422*0d24de9dSSimon Glass    lines = fd.readlines()
423*0d24de9dSSimon Glass    fd.close()
424*0d24de9dSSimon Glass
425*0d24de9dSSimon Glass    fd = open(fname, 'w')
426*0d24de9dSSimon Glass    text = series.cover
427*0d24de9dSSimon Glass    prefix = series.GetPatchPrefix()
428*0d24de9dSSimon Glass    for line in lines:
429*0d24de9dSSimon Glass        if line.startswith('Subject:'):
430*0d24de9dSSimon Glass            # TODO: if more than 10 patches this should save 00/xx, not 0/xx
431*0d24de9dSSimon Glass            line = 'Subject: [%s 0/%d] %s\n' % (prefix, count, text[0])
432*0d24de9dSSimon Glass
433*0d24de9dSSimon Glass        # Insert our cover letter
434*0d24de9dSSimon Glass        elif line.startswith('*** BLURB HERE ***'):
435*0d24de9dSSimon Glass            # First the blurb test
436*0d24de9dSSimon Glass            line = '\n'.join(text[1:]) + '\n'
437*0d24de9dSSimon Glass            if series.get('notes'):
438*0d24de9dSSimon Glass                line += '\n'.join(series.notes) + '\n'
439*0d24de9dSSimon Glass
440*0d24de9dSSimon Glass            # Now the change list
441*0d24de9dSSimon Glass            out = series.MakeChangeLog(None)
442*0d24de9dSSimon Glass            line += '\n' + '\n'.join(out)
443*0d24de9dSSimon Glass        fd.write(line)
444*0d24de9dSSimon Glass    fd.close()
445