policy.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. # vim: set fileencoding=utf-8 :
  2. #
  3. # (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com>
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, please see
  16. # <http://www.gnu.org/licenses/>
  17. """Default packaging policy for RPM"""
  18. import re
  19. from gbp.pkg import PkgPolicy, parse_archive_filename
  20. from gbp.scripts.common.pq import parse_gbp_commands
  21. class RpmPkgPolicy(PkgPolicy):
  22. """Packaging policy for RPM"""
  23. # Special rpmlib python module for GBP (only)
  24. python_rpmlib_module_name = "rpm"
  25. alnum = 'a-zA-Z0-9'
  26. # Valid characters for RPM pkg name
  27. name_whitelist_chars = '._+%{}\-'
  28. # Valid characters for RPM pkg version
  29. version_whitelist_chars = '._+%{}~'
  30. # Regexp for checking the validity of package name
  31. packagename_re = re.compile("^[%s][%s%s]+$" %
  32. (alnum, alnum, name_whitelist_chars))
  33. packagename_msg = ("Package names must be at least two characters long, "
  34. "start with an alphanumeric and can only contain "
  35. "alphanumerics or characters in %s" %
  36. list(name_whitelist_chars))
  37. # Regexp for checking the validity of package (upstream) version
  38. upstreamversion_re = re.compile("^[0-9][%s%s]*$" %
  39. (alnum, version_whitelist_chars))
  40. upstreamversion_msg = ("Upstream version numbers must start with a digit "
  41. "and can only containg alphanumerics or characters "
  42. "in %s" % list(version_whitelist_chars))
  43. @classmethod
  44. def is_valid_orig_archive(cls, filename):
  45. """
  46. Is this a valid orig source archive
  47. @param filename: upstream source archive filename
  48. @type filename: C{str}
  49. @return: true if valid upstream source archive filename
  50. @rtype: C{bool}
  51. >>> RpmPkgPolicy.is_valid_orig_archive("foo/bar_baz.tar.gz")
  52. True
  53. >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar.tar")
  54. True
  55. >>> RpmPkgPolicy.is_valid_orig_archive("foo.bar")
  56. False
  57. >>> RpmPkgPolicy.is_valid_orig_archive("foo.gz")
  58. False
  59. """
  60. _base, arch_fmt, _compression = parse_archive_filename(filename)
  61. if arch_fmt:
  62. return True
  63. return False
  64. class Changelog(object):
  65. """Container for changelog related policy settings"""
  66. # Regexps for splitting/parsing the changelog section (of
  67. # Tizen / Fedora style changelogs)
  68. section_match_re = r'^\*'
  69. section_split_re = r'^\*\s*(?P<ch_header>\S.*?)$\n(?P<ch_body>.*)'
  70. header_split_re = r'(?P<ch_time>\S.*\s[0-9]{4})\s+(?P<ch_name>\S.*$)'
  71. header_name_split_re = r'(?P<name>[^<]*)\s+<(?P<email>[^>]+)>((\s*-)?\s+(?P<revision>\S+))?$'
  72. body_name_re = r'\[(?P<name>.*)\]'
  73. # Changelog header format (when writing out changelog)
  74. header_format = "* %(time)s %(name)s <%(email)s> %(revision)s"
  75. header_time_format = "%a %b %d %Y"
  76. header_rev_format = "%(version)s"
  77. class ChangelogEntryFormatter(object):
  78. """Helper class for generating changelog entries from git commits"""
  79. # Maximum length for a changelog entry line
  80. max_entry_line_length = 76
  81. # Bug tracking system related meta tags recognized from git commit msg
  82. bts_meta_tags = ("Close", "Closes", "Fixes", "Fix")
  83. # Regexp for matching bug tracking system ids (e.g. "bgo#123")
  84. bug_id_re = r'[A-Za-z0-9#_\-]+'
  85. @classmethod
  86. def _parse_bts_tags(cls, lines, meta_tags):
  87. """
  88. Parse and filter out bug tracking system related meta tags from
  89. commit message.
  90. @param lines: commit message
  91. @type lines: C{list} of C{str}
  92. @param meta_tags: meta tags to look for
  93. @type meta_tags: C{tuple} of C{str}
  94. @return: bts-ids per meta tag and the non-mathced lines
  95. @rtype: (C{dict}, C{list} of C{str})
  96. """
  97. tags = {}
  98. other_lines = []
  99. bts_re = re.compile(r'^(?P<tag>%s):\s*(?P<ids>.*)' %
  100. ('|'.join(meta_tags)), re.I)
  101. bug_id_re = re.compile(cls.bug_id_re)
  102. for line in lines:
  103. match = bts_re.match(line)
  104. if match:
  105. tag = match.group('tag')
  106. ids_str = match.group('ids')
  107. bug_ids = [bug_id.strip() for bug_id in
  108. bug_id_re.findall(ids_str)]
  109. if tag in tags:
  110. tags[tag] += bug_ids
  111. else:
  112. tags[tag] = bug_ids
  113. else:
  114. other_lines.append(line)
  115. return (tags, other_lines)
  116. @classmethod
  117. def _extra_filter(cls, lines, ignore_re):
  118. """
  119. Filter out specific lines from the commit message.
  120. @param lines: commit message
  121. @type lines: C{list} of C{str}
  122. @param ignore_re: regexp for matching ignored lines
  123. @type ignore_re: C{str}
  124. @return: filtered commit message
  125. @rtype: C{list} of C{str}
  126. """
  127. if ignore_re:
  128. match = re.compile(ignore_re)
  129. return [line for line in lines if not match.match(line)]
  130. else:
  131. return lines
  132. @classmethod
  133. def compose(cls, commit_info, **kwargs):
  134. """
  135. Generate a changelog entry from a git commit.
  136. @param commit_info: info about the commit
  137. @type commit_info: C{commit_info} object from
  138. L{gbp.git.repository.GitRepository.get_commit_info()}.
  139. @param kwargs: additional arguments to the compose() method,
  140. currently we recognize 'full', 'id_len' and 'ignore_re'
  141. @type kwargs: C{dict}
  142. @return: formatted changelog entry
  143. @rtype: C{list} of C{str}
  144. """
  145. # Parse and filter out gbp command meta-tags
  146. cmds, body = parse_gbp_commands(commit_info, 'gbp-rpm-ch',
  147. ('ignore', 'short', 'full'), ())
  148. body = body.splitlines()
  149. if 'ignore' in cmds:
  150. return None
  151. # Parse and filter out bts-related meta-tags
  152. bts_tags, body = cls._parse_bts_tags(body, cls.bts_meta_tags)
  153. # Additional filtering
  154. body = cls._extra_filter(body, kwargs['ignore_re'])
  155. # Generate changelog entry
  156. subject = commit_info['subject']
  157. commitid = commit_info['id']
  158. if kwargs['id_len']:
  159. text = ["- [%s] %s" % (commitid[0:kwargs['id_len']], subject)]
  160. else:
  161. text = ["- %s" % subject]
  162. # Add all non-filtered-out lines from commit message, unless 'short'
  163. if (kwargs['full'] or 'full' in cmds) and 'short' not in cmds:
  164. # Add all non-blank body lines.
  165. text.extend([" " + line for line in body if line.strip()])
  166. # Add bts tags and ids in the end
  167. for tag, ids in bts_tags.iteritems():
  168. bts_msg = " (%s: %s)" % (tag, ', '.join(ids))
  169. if len(text[-1]) + len(bts_msg) >= cls.max_entry_line_length:
  170. text.append(" ")
  171. text[-1] += bts_msg
  172. return text