series.py 7.9 KB

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