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