series.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. import itertools
  22. import os
  23. import get_maintainer
  24. import gitutil
  25. import terminal
  26. # Series-xxx tags that we understand
  27. valid_series = ['to', 'cc', 'version', 'changes', 'prefix', 'notes', 'name'];
  28. class Series(dict):
  29. """Holds information about a patch series, including all tags.
  30. Vars:
  31. cc: List of aliases/emails to Cc all patches to
  32. commits: List of Commit objects, one for each patch
  33. cover: List of lines in the cover letter
  34. notes: List of lines in the notes
  35. changes: (dict) List of changes for each version, The key is
  36. the integer version number
  37. """
  38. def __init__(self):
  39. self.cc = []
  40. self.to = []
  41. self.commits = []
  42. self.cover = None
  43. self.notes = []
  44. self.changes = {}
  45. # Written in MakeCcFile()
  46. # key: name of patch file
  47. # value: list of email addresses
  48. self._generated_cc = {}
  49. # These make us more like a dictionary
  50. def __setattr__(self, name, value):
  51. self[name] = value
  52. def __getattr__(self, name):
  53. return self[name]
  54. def AddTag(self, commit, line, name, value):
  55. """Add a new Series-xxx tag along with its value.
  56. Args:
  57. line: Source line containing tag (useful for debug/error messages)
  58. name: Tag name (part after 'Series-')
  59. value: Tag value (part after 'Series-xxx: ')
  60. """
  61. # If we already have it, then add to our list
  62. if name in self:
  63. values = value.split(',')
  64. values = [str.strip() for str in values]
  65. if type(self[name]) != type([]):
  66. raise ValueError("In %s: line '%s': Cannot add another value "
  67. "'%s' to series '%s'" %
  68. (commit.hash, line, values, self[name]))
  69. self[name] += values
  70. # Otherwise just set the value
  71. elif name in valid_series:
  72. self[name] = value
  73. else:
  74. raise ValueError("In %s: line '%s': Unknown 'Series-%s': valid "
  75. "options are %s" % (commit.hash, line, name,
  76. ', '.join(valid_series)))
  77. def AddCommit(self, commit):
  78. """Add a commit into our list of commits
  79. We create a list of tags in the commit subject also.
  80. Args:
  81. commit: Commit object to add
  82. """
  83. commit.CheckTags()
  84. self.commits.append(commit)
  85. def ShowActions(self, args, cmd, process_tags):
  86. """Show what actions we will/would perform
  87. Args:
  88. args: List of patch files we created
  89. cmd: The git command we would have run
  90. process_tags: Process tags as if they were aliases
  91. """
  92. col = terminal.Color()
  93. print 'Dry run, so not doing much. But I would do this:'
  94. print
  95. print 'Send a total of %d patch%s with %scover letter.' % (
  96. len(args), '' if len(args) == 1 else 'es',
  97. self.get('cover') and 'a ' or 'no ')
  98. # TODO: Colour the patches according to whether they passed checks
  99. for upto in range(len(args)):
  100. commit = self.commits[upto]
  101. print col.Color(col.GREEN, ' %s' % args[upto])
  102. cc_list = list(self._generated_cc[commit.patch])
  103. # Skip items in To list
  104. if 'to' in self:
  105. try:
  106. map(cc_list.remove, gitutil.BuildEmailList(self.to))
  107. except ValueError:
  108. pass
  109. for email in cc_list:
  110. if email == None:
  111. email = col.Color(col.YELLOW, "<alias '%s' not found>"
  112. % tag)
  113. if email:
  114. print ' Cc: ',email
  115. print
  116. for item in gitutil.BuildEmailList(self.get('to', '<none>')):
  117. print 'To:\t ', item
  118. for item in gitutil.BuildEmailList(self.cc):
  119. print 'Cc:\t ', item
  120. print 'Version: ', self.get('version')
  121. print 'Prefix:\t ', self.get('prefix')
  122. if self.cover:
  123. print 'Cover: %d lines' % len(self.cover)
  124. all_ccs = itertools.chain(*self._generated_cc.values())
  125. for email in set(all_ccs):
  126. print ' Cc: ',email
  127. if cmd:
  128. print 'Git command: %s' % cmd
  129. def MakeChangeLog(self, commit):
  130. """Create a list of changes for each version.
  131. Return:
  132. The change log as a list of strings, one per line
  133. Changes in v4:
  134. - Jog the dial back closer to the widget
  135. Changes in v3: None
  136. Changes in v2:
  137. - Fix the widget
  138. - Jog the dial
  139. etc.
  140. """
  141. final = []
  142. need_blank = False
  143. for change in sorted(self.changes, reverse=True):
  144. out = []
  145. for this_commit, text in self.changes[change]:
  146. if commit and this_commit != commit:
  147. continue
  148. out.append(text)
  149. line = 'Changes in v%d:' % change
  150. have_changes = len(out) > 0
  151. if have_changes:
  152. out.insert(0, line)
  153. else:
  154. out = [line + ' None']
  155. if need_blank:
  156. out.insert(0, '')
  157. final += out
  158. need_blank = have_changes
  159. if self.changes:
  160. final.append('')
  161. return final
  162. def DoChecks(self):
  163. """Check that each version has a change log
  164. Print an error if something is wrong.
  165. """
  166. col = terminal.Color()
  167. if self.get('version'):
  168. changes_copy = dict(self.changes)
  169. for version in range(1, int(self.version) + 1):
  170. if self.changes.get(version):
  171. del changes_copy[version]
  172. else:
  173. if version > 1:
  174. str = 'Change log missing for v%d' % version
  175. print col.Color(col.RED, str)
  176. for version in changes_copy:
  177. str = 'Change log for unknown version v%d' % version
  178. print col.Color(col.RED, str)
  179. elif self.changes:
  180. str = 'Change log exists, but no version is set'
  181. print col.Color(col.RED, str)
  182. def MakeCcFile(self, process_tags, cover_fname):
  183. """Make a cc file for us to use for per-commit Cc automation
  184. Also stores in self._generated_cc to make ShowActions() faster.
  185. Args:
  186. process_tags: Process tags as if they were aliases
  187. cover_fname: If non-None the name of the cover letter.
  188. Return:
  189. Filename of temp file created
  190. """
  191. # Look for commit tags (of the form 'xxx:' at the start of the subject)
  192. fname = '/tmp/patman.%d' % os.getpid()
  193. fd = open(fname, 'w')
  194. all_ccs = []
  195. for commit in self.commits:
  196. list = []
  197. if process_tags:
  198. list += gitutil.BuildEmailList(commit.tags)
  199. list += gitutil.BuildEmailList(commit.cc_list)
  200. list += get_maintainer.GetMaintainer(commit.patch)
  201. all_ccs += list
  202. print >>fd, commit.patch, ', '.join(list)
  203. self._generated_cc[commit.patch] = list
  204. if cover_fname:
  205. print >>fd, cover_fname, ', '.join(set(all_ccs))
  206. fd.close()
  207. return fname
  208. def AddChange(self, version, commit, info):
  209. """Add a new change line to a version.
  210. This will later appear in the change log.
  211. Args:
  212. version: version number to add change list to
  213. info: change line for this version
  214. """
  215. if not self.changes.get(version):
  216. self.changes[version] = []
  217. self.changes[version].append([commit, info])
  218. def GetPatchPrefix(self):
  219. """Get the patch version string
  220. Return:
  221. Patch string, like 'RFC PATCH v5' or just 'PATCH'
  222. """
  223. version = ''
  224. if self.get('version'):
  225. version = ' v%s' % self['version']
  226. # Get patch name prefix
  227. prefix = ''
  228. if self.get('prefix'):
  229. prefix = '%s ' % self['prefix']
  230. return '%sPATCH%s' % (prefix, version)