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 # 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 v2: 149 - Jog the dial back closer to the widget 150 151 Changes in v1: 152 - Fix the widget 153 - Jog the dial 154 155 etc. 156 """ 157 final = [] 158 need_blank = False 159 for change in sorted(self.changes, reverse=True): 160 out = [] 161 for this_commit, text in self.changes[change]: 162 if commit and this_commit != commit: 163 continue 164 out.append(text) 165 if out: 166 out = ['Changes in v%d:' % change] + out 167 if need_blank: 168 out = [''] + out 169 final += out 170 need_blank = True 171 if self.changes: 172 final.append('') 173 return final 174 175 def DoChecks(self): 176 """Check that each version has a change log 177 178 Print an error if something is wrong. 179 """ 180 col = terminal.Color() 181 if self.get('version'): 182 changes_copy = dict(self.changes) 183 for version in range(1, int(self.version) + 1): 184 if self.changes.get(version): 185 del changes_copy[version] 186 else: 187 if version > 1: 188 str = 'Change log missing for v%d' % version 189 print col.Color(col.RED, str) 190 for version in changes_copy: 191 str = 'Change log for unknown version v%d' % version 192 print col.Color(col.RED, str) 193 elif self.changes: 194 str = 'Change log exists, but no version is set' 195 print col.Color(col.RED, str) 196 197 def MakeCcFile(self, process_tags): 198 """Make a cc file for us to use for per-commit Cc automation 199 200 Args: 201 process_tags: Process tags as if they were aliases 202 Return: 203 Filename of temp file created 204 """ 205 # Look for commit tags (of the form 'xxx:' at the start of the subject) 206 fname = '/tmp/patman.%d' % os.getpid() 207 fd = open(fname, 'w') 208 for commit in self.commits: 209 list = [] 210 if process_tags: 211 list += gitutil.BuildEmailList(commit.tags) 212 list += gitutil.BuildEmailList(commit.cc_list) 213 print >>fd, commit.patch, ', '.join(list) 214 215 fd.close() 216 return fname 217 218 def AddChange(self, version, commit, info): 219 """Add a new change line to a version. 220 221 This will later appear in the change log. 222 223 Args: 224 version: version number to add change list to 225 info: change line for this version 226 """ 227 if not self.changes.get(version): 228 self.changes[version] = [] 229 self.changes[version].append([commit, info]) 230 231 def GetPatchPrefix(self): 232 """Get the patch version string 233 234 Return: 235 Patch string, like 'RFC PATCH v5' or just 'PATCH' 236 """ 237 version = '' 238 if self.get('version'): 239 version = ' v%s' % self['version'] 240 241 # Get patch name prefix 242 prefix = '' 243 if self.get('prefix'): 244 prefix = '%s ' % self['prefix'] 245 return '%sPATCH%s' % (prefix, version) 246