blob: 85ed31688a1051d0d06579f104b859bcafa4e2be [file] [log] [blame]
Simon Glass0d24de92012-01-14 15:12:45 +00001# 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
Doug Anderson31187252012-12-03 14:40:43 +000022import itertools
Simon Glass0d24de92012-01-14 15:12:45 +000023import os
24
Doug Anderson21a19d72012-12-03 14:43:16 +000025import get_maintainer
Simon Glass0d24de92012-01-14 15:12:45 +000026import gitutil
27import terminal
28
29# Series-xxx tags that we understand
Simon Glassfe2f8d92013-03-20 16:43:00 +000030valid_series = ['to', 'cc', 'version', 'changes', 'prefix', 'notes', 'name',
Simon Glass645b2712013-03-26 13:09:44 +000031 'cover-cc', 'process_log']
Simon Glass0d24de92012-01-14 15:12:45 +000032
33class Series(dict):
34 """Holds information about a patch series, including all tags.
35
36 Vars:
37 cc: List of aliases/emails to Cc all patches to
38 commits: List of Commit objects, one for each patch
39 cover: List of lines in the cover letter
40 notes: List of lines in the notes
41 changes: (dict) List of changes for each version, The key is
42 the integer version number
Simon Glassf0b739f2013-05-02 14:46:02 +000043 allow_overwrite: Allow tags to overwrite an existing tag
Simon Glass0d24de92012-01-14 15:12:45 +000044 """
45 def __init__(self):
46 self.cc = []
47 self.to = []
Simon Glassfe2f8d92013-03-20 16:43:00 +000048 self.cover_cc = []
Simon Glass0d24de92012-01-14 15:12:45 +000049 self.commits = []
50 self.cover = None
51 self.notes = []
52 self.changes = {}
Simon Glassf0b739f2013-05-02 14:46:02 +000053 self.allow_overwrite = False
Simon Glass0d24de92012-01-14 15:12:45 +000054
Doug Andersond94566a2012-12-03 14:40:42 +000055 # Written in MakeCcFile()
56 # key: name of patch file
57 # value: list of email addresses
58 self._generated_cc = {}
59
Simon Glass0d24de92012-01-14 15:12:45 +000060 # These make us more like a dictionary
61 def __setattr__(self, name, value):
62 self[name] = value
63
64 def __getattr__(self, name):
65 return self[name]
66
67 def AddTag(self, commit, line, name, value):
68 """Add a new Series-xxx tag along with its value.
69
70 Args:
71 line: Source line containing tag (useful for debug/error messages)
72 name: Tag name (part after 'Series-')
73 value: Tag value (part after 'Series-xxx: ')
74 """
75 # If we already have it, then add to our list
Simon Glassfe2f8d92013-03-20 16:43:00 +000076 name = name.replace('-', '_')
Simon Glassf0b739f2013-05-02 14:46:02 +000077 if name in self and not self.allow_overwrite:
Simon Glass0d24de92012-01-14 15:12:45 +000078 values = value.split(',')
79 values = [str.strip() for str in values]
80 if type(self[name]) != type([]):
81 raise ValueError("In %s: line '%s': Cannot add another value "
82 "'%s' to series '%s'" %
83 (commit.hash, line, values, self[name]))
84 self[name] += values
85
86 # Otherwise just set the value
87 elif name in valid_series:
88 self[name] = value
89 else:
90 raise ValueError("In %s: line '%s': Unknown 'Series-%s': valid "
Simon Glassef0e9de2012-09-27 15:06:02 +000091 "options are %s" % (commit.hash, line, name,
Simon Glass0d24de92012-01-14 15:12:45 +000092 ', '.join(valid_series)))
93
94 def AddCommit(self, commit):
95 """Add a commit into our list of commits
96
97 We create a list of tags in the commit subject also.
98
99 Args:
100 commit: Commit object to add
101 """
102 commit.CheckTags()
103 self.commits.append(commit)
104
105 def ShowActions(self, args, cmd, process_tags):
106 """Show what actions we will/would perform
107
108 Args:
109 args: List of patch files we created
110 cmd: The git command we would have run
111 process_tags: Process tags as if they were aliases
112 """
113 col = terminal.Color()
114 print 'Dry run, so not doing much. But I would do this:'
115 print
116 print 'Send a total of %d patch%s with %scover letter.' % (
117 len(args), '' if len(args) == 1 else 'es',
118 self.get('cover') and 'a ' or 'no ')
119
120 # TODO: Colour the patches according to whether they passed checks
121 for upto in range(len(args)):
122 commit = self.commits[upto]
123 print col.Color(col.GREEN, ' %s' % args[upto])
Doug Andersond94566a2012-12-03 14:40:42 +0000124 cc_list = list(self._generated_cc[commit.patch])
Simon Glass0d24de92012-01-14 15:12:45 +0000125
Otavio Salvador43de0242012-08-18 07:19:51 +0000126 # Skip items in To list
127 if 'to' in self:
128 try:
129 map(cc_list.remove, gitutil.BuildEmailList(self.to))
130 except ValueError:
131 pass
132
Simon Glass0d24de92012-01-14 15:12:45 +0000133 for email in cc_list:
134 if email == None:
135 email = col.Color(col.YELLOW, "<alias '%s' not found>"
136 % tag)
137 if email:
138 print ' Cc: ',email
139 print
140 for item in gitutil.BuildEmailList(self.get('to', '<none>')):
141 print 'To:\t ', item
142 for item in gitutil.BuildEmailList(self.cc):
143 print 'Cc:\t ', item
144 print 'Version: ', self.get('version')
145 print 'Prefix:\t ', self.get('prefix')
146 if self.cover:
147 print 'Cover: %d lines' % len(self.cover)
Simon Glassfe2f8d92013-03-20 16:43:00 +0000148 cover_cc = gitutil.BuildEmailList(self.get('cover_cc', ''))
149 all_ccs = itertools.chain(cover_cc, *self._generated_cc.values())
Doug Anderson31187252012-12-03 14:40:43 +0000150 for email in set(all_ccs):
151 print ' Cc: ',email
Simon Glass0d24de92012-01-14 15:12:45 +0000152 if cmd:
153 print 'Git command: %s' % cmd
154
155 def MakeChangeLog(self, commit):
156 """Create a list of changes for each version.
157
158 Return:
159 The change log as a list of strings, one per line
160
Simon Glass27e97602012-10-30 06:15:16 +0000161 Changes in v4:
Otavio Salvador244e6f92012-08-18 07:46:04 +0000162 - Jog the dial back closer to the widget
163
Simon Glass27e97602012-10-30 06:15:16 +0000164 Changes in v3: None
165 Changes in v2:
Simon Glass0d24de92012-01-14 15:12:45 +0000166 - Fix the widget
167 - Jog the dial
168
Simon Glass0d24de92012-01-14 15:12:45 +0000169 etc.
170 """
171 final = []
Simon Glass645b2712013-03-26 13:09:44 +0000172 process_it = self.get('process_log', '').split(',')
173 process_it = [item.strip() for item in process_it]
Simon Glass0d24de92012-01-14 15:12:45 +0000174 need_blank = False
Otavio Salvador244e6f92012-08-18 07:46:04 +0000175 for change in sorted(self.changes, reverse=True):
Simon Glass0d24de92012-01-14 15:12:45 +0000176 out = []
177 for this_commit, text in self.changes[change]:
178 if commit and this_commit != commit:
179 continue
Simon Glass645b2712013-03-26 13:09:44 +0000180 if 'uniq' not in process_it or text not in out:
181 out.append(text)
Simon Glass27e97602012-10-30 06:15:16 +0000182 line = 'Changes in v%d:' % change
183 have_changes = len(out) > 0
Simon Glass645b2712013-03-26 13:09:44 +0000184 if 'sort' in process_it:
185 out = sorted(out)
Simon Glass27e97602012-10-30 06:15:16 +0000186 if have_changes:
187 out.insert(0, line)
188 else:
189 out = [line + ' None']
190 if need_blank:
191 out.insert(0, '')
192 final += out
193 need_blank = have_changes
Simon Glass0d24de92012-01-14 15:12:45 +0000194 if self.changes:
195 final.append('')
196 return final
197
198 def DoChecks(self):
199 """Check that each version has a change log
200
201 Print an error if something is wrong.
202 """
203 col = terminal.Color()
204 if self.get('version'):
205 changes_copy = dict(self.changes)
Otavio Salvadord5f81d82012-08-13 10:08:22 +0000206 for version in range(1, int(self.version) + 1):
Simon Glass0d24de92012-01-14 15:12:45 +0000207 if self.changes.get(version):
208 del changes_copy[version]
209 else:
Otavio Salvadord5f81d82012-08-13 10:08:22 +0000210 if version > 1:
211 str = 'Change log missing for v%d' % version
212 print col.Color(col.RED, str)
Simon Glass0d24de92012-01-14 15:12:45 +0000213 for version in changes_copy:
214 str = 'Change log for unknown version v%d' % version
215 print col.Color(col.RED, str)
216 elif self.changes:
217 str = 'Change log exists, but no version is set'
218 print col.Color(col.RED, str)
219
Simon Glassa1318f72013-03-26 13:09:42 +0000220 def MakeCcFile(self, process_tags, cover_fname, raise_on_error):
Simon Glass0d24de92012-01-14 15:12:45 +0000221 """Make a cc file for us to use for per-commit Cc automation
222
Doug Andersond94566a2012-12-03 14:40:42 +0000223 Also stores in self._generated_cc to make ShowActions() faster.
224
Simon Glass0d24de92012-01-14 15:12:45 +0000225 Args:
226 process_tags: Process tags as if they were aliases
Doug Anderson31187252012-12-03 14:40:43 +0000227 cover_fname: If non-None the name of the cover letter.
Simon Glassa1318f72013-03-26 13:09:42 +0000228 raise_on_error: True to raise an error when an alias fails to match,
229 False to just print a message.
Simon Glass0d24de92012-01-14 15:12:45 +0000230 Return:
231 Filename of temp file created
232 """
233 # Look for commit tags (of the form 'xxx:' at the start of the subject)
234 fname = '/tmp/patman.%d' % os.getpid()
235 fd = open(fname, 'w')
Doug Anderson31187252012-12-03 14:40:43 +0000236 all_ccs = []
Simon Glass0d24de92012-01-14 15:12:45 +0000237 for commit in self.commits:
238 list = []
239 if process_tags:
Simon Glassa1318f72013-03-26 13:09:42 +0000240 list += gitutil.BuildEmailList(commit.tags,
241 raise_on_error=raise_on_error)
242 list += gitutil.BuildEmailList(commit.cc_list,
243 raise_on_error=raise_on_error)
Doug Anderson21a19d72012-12-03 14:43:16 +0000244 list += get_maintainer.GetMaintainer(commit.patch)
Doug Anderson31187252012-12-03 14:40:43 +0000245 all_ccs += list
Simon Glass0d24de92012-01-14 15:12:45 +0000246 print >>fd, commit.patch, ', '.join(list)
Doug Andersond94566a2012-12-03 14:40:42 +0000247 self._generated_cc[commit.patch] = list
Simon Glass0d24de92012-01-14 15:12:45 +0000248
Doug Anderson31187252012-12-03 14:40:43 +0000249 if cover_fname:
Simon Glassfe2f8d92013-03-20 16:43:00 +0000250 cover_cc = gitutil.BuildEmailList(self.get('cover_cc', ''))
251 print >>fd, cover_fname, ', '.join(set(cover_cc + all_ccs))
Doug Anderson31187252012-12-03 14:40:43 +0000252
Simon Glass0d24de92012-01-14 15:12:45 +0000253 fd.close()
254 return fname
255
256 def AddChange(self, version, commit, info):
257 """Add a new change line to a version.
258
259 This will later appear in the change log.
260
261 Args:
262 version: version number to add change list to
263 info: change line for this version
264 """
265 if not self.changes.get(version):
266 self.changes[version] = []
267 self.changes[version].append([commit, info])
268
269 def GetPatchPrefix(self):
270 """Get the patch version string
271
272 Return:
273 Patch string, like 'RFC PATCH v5' or just 'PATCH'
274 """
275 version = ''
276 if self.get('version'):
277 version = ' v%s' % self['version']
278
279 # Get patch name prefix
280 prefix = ''
281 if self.get('prefix'):
282 prefix = '%s ' % self['prefix']
283 return '%sPATCH%s' % (prefix, version)