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