series.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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', 'name'];
  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" % (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. # Skip items in To list
  101. if 'to' in self:
  102. try:
  103. map(cc_list.remove, gitutil.BuildEmailList(self.to))
  104. except ValueError:
  105. pass
  106. for email in cc_list:
  107. if email == None:
  108. email = col.Color(col.YELLOW, "<alias '%s' not found>"
  109. % tag)
  110. if email:
  111. print ' Cc: ',email
  112. print
  113. for item in gitutil.BuildEmailList(self.get('to', '<none>')):
  114. print 'To:\t ', item
  115. for item in gitutil.BuildEmailList(self.cc):
  116. print 'Cc:\t ', item
  117. print 'Version: ', self.get('version')
  118. print 'Prefix:\t ', self.get('prefix')
  119. if self.cover:
  120. print 'Cover: %d lines' % len(self.cover)
  121. if cmd:
  122. print 'Git command: %s' % cmd
  123. def MakeChangeLog(self, commit):
  124. """Create a list of changes for each version.
  125. Return:
  126. The change log as a list of strings, one per line
  127. Changes in v2:
  128. - Jog the dial back closer to the widget
  129. Changes in v1:
  130. - Fix the widget
  131. - Jog the dial
  132. etc.
  133. """
  134. final = []
  135. need_blank = False
  136. for change in sorted(self.changes, reverse=True):
  137. out = []
  138. for this_commit, text in self.changes[change]:
  139. if commit and this_commit != commit:
  140. continue
  141. out.append(text)
  142. if out:
  143. out = ['Changes in v%d:' % change] + out
  144. if need_blank:
  145. out = [''] + out
  146. final += out
  147. need_blank = True
  148. if self.changes:
  149. final.append('')
  150. return final
  151. def DoChecks(self):
  152. """Check that each version has a change log
  153. Print an error if something is wrong.
  154. """
  155. col = terminal.Color()
  156. if self.get('version'):
  157. changes_copy = dict(self.changes)
  158. for version in range(1, int(self.version) + 1):
  159. if self.changes.get(version):
  160. del changes_copy[version]
  161. else:
  162. if version > 1:
  163. str = 'Change log missing for v%d' % version
  164. print col.Color(col.RED, str)
  165. for version in changes_copy:
  166. str = 'Change log for unknown version v%d' % version
  167. print col.Color(col.RED, str)
  168. elif self.changes:
  169. str = 'Change log exists, but no version is set'
  170. print col.Color(col.RED, str)
  171. def MakeCcFile(self, process_tags):
  172. """Make a cc file for us to use for per-commit Cc automation
  173. Args:
  174. process_tags: Process tags as if they were aliases
  175. Return:
  176. Filename of temp file created
  177. """
  178. # Look for commit tags (of the form 'xxx:' at the start of the subject)
  179. fname = '/tmp/patman.%d' % os.getpid()
  180. fd = open(fname, 'w')
  181. for commit in self.commits:
  182. list = []
  183. if process_tags:
  184. list += gitutil.BuildEmailList(commit.tags)
  185. list += gitutil.BuildEmailList(commit.cc_list)
  186. print >>fd, commit.patch, ', '.join(list)
  187. fd.close()
  188. return fname
  189. def AddChange(self, version, commit, info):
  190. """Add a new change line to a version.
  191. This will later appear in the change log.
  192. Args:
  193. version: version number to add change list to
  194. info: change line for this version
  195. """
  196. if not self.changes.get(version):
  197. self.changes[version] = []
  198. self.changes[version].append([commit, info])
  199. def GetPatchPrefix(self):
  200. """Get the patch version string
  201. Return:
  202. Patch string, like 'RFC PATCH v5' or just 'PATCH'
  203. """
  204. version = ''
  205. if self.get('version'):
  206. version = ' v%s' % self['version']
  207. # Get patch name prefix
  208. prefix = ''
  209. if self.get('prefix'):
  210. prefix = '%s ' % self['prefix']
  211. return '%sPATCH%s' % (prefix, version)