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