1# Copyright (c) 2011 The Chromium OS Authors. 2# 3# See file CREDITS for list of people who contributed to this 4# project. 5# 6# This program is free software; you can redistribute it and/or 7# modify it under the terms of the GNU General Public License as 8# published by the Free Software Foundation; either version 2 of 9# the License, or (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, 19# MA 02111-1307 USA 20# 21 22import os 23 24import gitutil 25import terminal 26 27# Series-xxx tags that we understand 28valid_series = ['to', 'cc', 'version', 'changes', 'prefix', 'notes']; 29 30class Series(dict): 31 """Holds information about a patch series, including all tags. 32 33 Vars: 34 cc: List of aliases/emails to Cc all patches to 35 commits: List of Commit objects, one for each patch 36 cover: List of lines in the cover letter 37 notes: List of lines in the notes 38 changes: (dict) List of changes for each version, The key is 39 the integer version number 40 """ 41 def __init__(self): 42 self.cc = [] 43 self.to = [] 44 self.commits = [] 45 self.cover = None 46 self.notes = [] 47 self.changes = {} 48 49 # These make us more like a dictionary 50 def __setattr__(self, name, value): 51 self[name] = value 52 53 def __getattr__(self, name): 54 return self[name] 55 56 def AddTag(self, commit, line, name, value): 57 """Add a new Series-xxx tag along with its value. 58 59 Args: 60 line: Source line containing tag (useful for debug/error messages) 61 name: Tag name (part after 'Series-') 62 value: Tag value (part after 'Series-xxx: ') 63 """ 64 # If we already have it, then add to our list 65 if name in self: 66 values = value.split(',') 67 values = [str.strip() for str in values] 68 if type(self[name]) != type([]): 69 raise ValueError("In %s: line '%s': Cannot add another value " 70 "'%s' to series '%s'" % 71 (commit.hash, line, values, self[name])) 72 self[name] += values 73 74 # Otherwise just set the value 75 elif name in valid_series: 76 self[name] = value 77 else: 78 raise ValueError("In %s: line '%s': Unknown 'Series-%s': valid " 79 "options are %s" % (self.commit.hash, line, name, 80 ', '.join(valid_series))) 81 82 def AddCommit(self, commit): 83 """Add a commit into our list of commits 84 85 We create a list of tags in the commit subject also. 86 87 Args: 88 commit: Commit object to add 89 """ 90 commit.CheckTags() 91 self.commits.append(commit) 92 93 def ShowActions(self, args, cmd, process_tags): 94 """Show what actions we will/would perform 95 96 Args: 97 args: List of patch files we created 98 cmd: The git command we would have run 99 process_tags: Process tags as if they were aliases 100 """ 101 col = terminal.Color() 102 print 'Dry run, so not doing much. But I would do this:' 103 print 104 print 'Send a total of %d patch%s with %scover letter.' % ( 105 len(args), '' if len(args) == 1 else 'es', 106 self.get('cover') and 'a ' or 'no ') 107 108 # TODO: Colour the patches according to whether they passed checks 109 for upto in range(len(args)): 110 commit = self.commits[upto] 111 print col.Color(col.GREEN, ' %s' % args[upto]) 112 cc_list = [] 113 if process_tags: 114 cc_list += gitutil.BuildEmailList(commit.tags) 115 cc_list += gitutil.BuildEmailList(commit.cc_list) 116 117 for email in cc_list: 118 if email == None: 119 email = col.Color(col.YELLOW, "<alias '%s' not found>" 120 % tag) 121 if email: 122 print ' Cc: ',email 123 print 124 for item in gitutil.BuildEmailList(self.get('to', '<none>')): 125 print 'To:\t ', item 126 for item in gitutil.BuildEmailList(self.cc): 127 print 'Cc:\t ', item 128 print 'Version: ', self.get('version') 129 print 'Prefix:\t ', self.get('prefix') 130 if self.cover: 131 print 'Cover: %d lines' % len(self.cover) 132 if cmd: 133 print 'Git command: %s' % cmd 134 135 def MakeChangeLog(self, commit): 136 """Create a list of changes for each version. 137 138 Return: 139 The change log as a list of strings, one per line 140 141 Changes in v1: 142 - Fix the widget 143 - Jog the dial 144 145 Changes in v2: 146 - Jog the dial back closer to the widget 147 148 etc. 149 """ 150 final = [] 151 need_blank = False 152 for change in sorted(self.changes): 153 out = [] 154 for this_commit, text in self.changes[change]: 155 if commit and this_commit != commit: 156 continue 157 out.append(text) 158 if out: 159 out = ['Changes in v%d:' % change] + out 160 if need_blank: 161 out = [''] + out 162 final += out 163 need_blank = True 164 if self.changes: 165 final.append('') 166 return final 167 168 def DoChecks(self): 169 """Check that each version has a change log 170 171 Print an error if something is wrong. 172 """ 173 col = terminal.Color() 174 if self.get('version'): 175 changes_copy = dict(self.changes) 176 for version in range(2, int(self.version) + 1): 177 if self.changes.get(version): 178 del changes_copy[version] 179 else: 180 str = 'Change log missing for v%d' % version 181 print col.Color(col.RED, str) 182 for version in changes_copy: 183 str = 'Change log for unknown version v%d' % version 184 print col.Color(col.RED, str) 185 elif self.changes: 186 str = 'Change log exists, but no version is set' 187 print col.Color(col.RED, str) 188 189 def MakeCcFile(self, process_tags): 190 """Make a cc file for us to use for per-commit Cc automation 191 192 Args: 193 process_tags: Process tags as if they were aliases 194 Return: 195 Filename of temp file created 196 """ 197 # Look for commit tags (of the form 'xxx:' at the start of the subject) 198 fname = '/tmp/patman.%d' % os.getpid() 199 fd = open(fname, 'w') 200 for commit in self.commits: 201 list = [] 202 if process_tags: 203 list += gitutil.BuildEmailList(commit.tags) 204 list += gitutil.BuildEmailList(commit.cc_list) 205 print >>fd, commit.patch, ', '.join(list) 206 207 fd.close() 208 return fname 209 210 def AddChange(self, version, commit, info): 211 """Add a new change line to a version. 212 213 This will later appear in the change log. 214 215 Args: 216 version: version number to add change list to 217 info: change line for this version 218 """ 219 if not self.changes.get(version): 220 self.changes[version] = [] 221 self.changes[version].append([commit, info]) 222 223 def GetPatchPrefix(self): 224 """Get the patch version string 225 226 Return: 227 Patch string, like 'RFC PATCH v5' or just 'PATCH' 228 """ 229 version = '' 230 if self.get('version'): 231 version = ' v%s' % self['version'] 232 233 # Get patch name prefix 234 prefix = '' 235 if self.get('prefix'): 236 prefix = '%s ' % self['prefix'] 237 return '%sPATCH%s' % (prefix, version) 238