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', 'name']; 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" % (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 # Skip items in To list 118 if 'to' in self: 119 try: 120 map(cc_list.remove, gitutil.BuildEmailList(self.to)) 121 except ValueError: 122 pass 123 124 for email in cc_list: 125 if email == None: 126 email = col.Color(col.YELLOW, "<alias '%s' not found>" 127 % tag) 128 if email: 129 print ' Cc: ',email 130 print 131 for item in gitutil.BuildEmailList(self.get('to', '<none>')): 132 print 'To:\t ', item 133 for item in gitutil.BuildEmailList(self.cc): 134 print 'Cc:\t ', item 135 print 'Version: ', self.get('version') 136 print 'Prefix:\t ', self.get('prefix') 137 if self.cover: 138 print 'Cover: %d lines' % len(self.cover) 139 if cmd: 140 print 'Git command: %s' % cmd 141 142 def MakeChangeLog(self, commit): 143 """Create a list of changes for each version. 144 145 Return: 146 The change log as a list of strings, one per line 147 148 Changes in v4: 149 - Jog the dial back closer to the widget 150 151 Changes in v3: None 152 Changes in v2: 153 - Fix the widget 154 - Jog the dial 155 156 etc. 157 """ 158 final = [] 159 need_blank = False 160 for change in sorted(self.changes, reverse=True): 161 out = [] 162 for this_commit, text in self.changes[change]: 163 if commit and this_commit != commit: 164 continue 165 out.append(text) 166 line = 'Changes in v%d:' % change 167 have_changes = len(out) > 0 168 if have_changes: 169 out.insert(0, line) 170 else: 171 out = [line + ' None'] 172 if need_blank: 173 out.insert(0, '') 174 final += out 175 need_blank = have_changes 176 if self.changes: 177 final.append('') 178 return final 179 180 def DoChecks(self): 181 """Check that each version has a change log 182 183 Print an error if something is wrong. 184 """ 185 col = terminal.Color() 186 if self.get('version'): 187 changes_copy = dict(self.changes) 188 for version in range(1, int(self.version) + 1): 189 if self.changes.get(version): 190 del changes_copy[version] 191 else: 192 if version > 1: 193 str = 'Change log missing for v%d' % version 194 print col.Color(col.RED, str) 195 for version in changes_copy: 196 str = 'Change log for unknown version v%d' % version 197 print col.Color(col.RED, str) 198 elif self.changes: 199 str = 'Change log exists, but no version is set' 200 print col.Color(col.RED, str) 201 202 def MakeCcFile(self, process_tags): 203 """Make a cc file for us to use for per-commit Cc automation 204 205 Args: 206 process_tags: Process tags as if they were aliases 207 Return: 208 Filename of temp file created 209 """ 210 # Look for commit tags (of the form 'xxx:' at the start of the subject) 211 fname = '/tmp/patman.%d' % os.getpid() 212 fd = open(fname, 'w') 213 for commit in self.commits: 214 list = [] 215 if process_tags: 216 list += gitutil.BuildEmailList(commit.tags) 217 list += gitutil.BuildEmailList(commit.cc_list) 218 print >>fd, commit.patch, ', '.join(list) 219 220 fd.close() 221 return fname 222 223 def AddChange(self, version, commit, info): 224 """Add a new change line to a version. 225 226 This will later appear in the change log. 227 228 Args: 229 version: version number to add change list to 230 info: change line for this version 231 """ 232 if not self.changes.get(version): 233 self.changes[version] = [] 234 self.changes[version].append([commit, info]) 235 236 def GetPatchPrefix(self): 237 """Get the patch version string 238 239 Return: 240 Patch string, like 'RFC PATCH v5' or just 'PATCH' 241 """ 242 version = '' 243 if self.get('version'): 244 version = ' v%s' % self['version'] 245 246 # Get patch name prefix 247 prefix = '' 248 if self.get('prefix'): 249 prefix = '%s ' % self['prefix'] 250 return '%sPATCH%s' % (prefix, version) 251