dch.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # vim: set fileencoding=utf-8 :
  2. #
  3. # (C) 2010 Rob Browning <rlb@defaultvalue.org>
  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. """provides git-dch helpers"""
  18. import re
  19. MAX_CHANGELOG_LINE_LENGTH = 76
  20. def extract_git_dch_cmds(lines, options):
  21. """Return a dictionary of all Git-Dch: commands found in lines.
  22. The command keys will be lowercased, i.e. {'ignore' : True,
  23. 'short': True}. For now, all the options are binary. Also return
  24. all of the lines that do not contain Git-Dch: commands."""
  25. commands = {}
  26. other_lines = []
  27. for line in lines:
  28. if line.startswith('Git-Dch: ') or line.startswith('Gbp-Dch: '):
  29. cmd = line.split(' ', 1)[1].strip().lower()
  30. commands[cmd] = True
  31. else:
  32. other_lines.append(line)
  33. return (commands, other_lines)
  34. def filter_ignore_rx_matches(lines, options):
  35. """Filter any lines that match options.ignore_regex
  36. (i.e. --ignore-regex)."""
  37. if options.ignore_regex:
  38. ignore_re = re.compile(options.ignore_regex)
  39. return [line for line in lines if not ignore_re.match(line)]
  40. else:
  41. return lines
  42. def extract_bts_cmds(lines, opts):
  43. """Return a dictionary of the bug tracking system commands
  44. contained in the the given lines. i.e. {'closed' : [1], 'fixed':
  45. [3, 4]}. Right now, this will only notice a single directive
  46. clause on a line. Also return all of the lines that do not
  47. contain bug tracking system commands."""
  48. _bug_re = re.compile(opts.meta_closes_bugnum, re.I)
  49. bts_rx = re.compile(r'(?P<bts>%s):\s+%s' % (opts.meta_closes, opts.meta_closes_bugnum), re.I)
  50. commands = {}
  51. other_lines = []
  52. for line in lines:
  53. m = bts_rx.match(line)
  54. if m:
  55. bug_nums = [bug.strip() for bug in _bug_re.findall(line, re.I)]
  56. try:
  57. commands[m.group('bts')] += bug_nums
  58. except KeyError:
  59. commands[m.group('bts')] = bug_nums
  60. else:
  61. other_lines.append(line)
  62. return (commands, other_lines)
  63. def extract_thanks_info(lines, options):
  64. """Return a list of all of the Thanks: entries, and a list of all
  65. of the lines that do not contain Thanks: entries."""
  66. thanks = []
  67. thanks_re = re.compile(r'thanks:\s+', re.I)
  68. other_lines = []
  69. for line in lines:
  70. if thanks_re.match(line):
  71. thanks.append(line.split(' ', 1)[1].strip())
  72. else:
  73. other_lines.append(line)
  74. return (thanks, other_lines)
  75. def _ispunct(ch):
  76. return not ch.isalnum() and not ch.isspace()
  77. def terminate_first_line_if_needed(lines):
  78. """Terminate the first line of lines with a '.' if multi-line."""
  79. # Don't add a period to empty or one line commit messages.
  80. if len(lines) < 2:
  81. return lines
  82. if lines[0] and _ispunct(lines[0][-1]):
  83. return lines
  84. if lines[1] and (_ispunct(lines[1][0]) or lines[1][0].islower()):
  85. return lines
  86. return [lines[0] + "."] + lines[1:]
  87. def format_changelog_entry(commit_info, options, last_commit=False):
  88. """Return a list of lines (without newlines) as the changelog
  89. entry for commit_info (generated by
  90. GitRepository.get_commit_info()). If last_commit is not False,
  91. then this entry is the last one in the series."""
  92. entry = [commit_info['subject']]
  93. body = commit_info['body'].splitlines()
  94. commitid = commit_info['id']
  95. (git_dch_cmds, body) = extract_git_dch_cmds(body, options)
  96. if 'ignore' in git_dch_cmds:
  97. return None
  98. if options.idlen:
  99. entry[0] = '[%s] ' % commitid[0:options.idlen] + entry[0]
  100. bts_cmds = {}
  101. thanks = []
  102. if options.meta:
  103. (bts_cmds, body) = extract_bts_cmds(body, options)
  104. (thanks, body) = extract_thanks_info(body, options)
  105. body = filter_ignore_rx_matches(body, options)
  106. if 'full' in git_dch_cmds or (options.full and 'short' not in git_dch_cmds):
  107. # Add all non-blank body lines.
  108. entry.extend([line for line in body if line.strip()])
  109. if thanks:
  110. # Last wins for now (match old behavior).
  111. thanks_msg = 'Thanks to %s' % thanks[-1]
  112. entry.extend([thanks_msg])
  113. for bts in bts_cmds:
  114. bts_msg = '(%s: %s)' % (bts, ', '.join(bts_cmds[bts]))
  115. if len(entry[-1]) + len(bts_msg) >= MAX_CHANGELOG_LINE_LENGTH:
  116. entry.extend([''])
  117. else:
  118. entry[-1] += " "
  119. entry[-1] += bts_msg
  120. entry = terminate_first_line_if_needed(entry)
  121. return entry